Benutzer-Wechsel in einem Package

Immer wieder möchte man aus einem PL/SQL-Package heraus einen Befehl absetzen, der nicht unter dem aktuellen sondern unter einem anderen Benutzer abgesetzt wird. Das ist so ohne weiteres nicht möglich, da ein Package einen Reconnect oder Benutzer-Wechsel nicht unterstützt. Hier leistet der Oracle Scheduler wertvolle Hilfe. Man kann mit ihm aus einem Package heraus einen synchronen Job aufrufen, der unter einem beliebigen Benutzer gestartet werden kann.

Hierzu ein Beispiel:

create or replace package my_utilities
is
--
-- Ausführen eines beliebigen SQL-Statements
procedure exec_with_other_user(p_sql in varchar2);
--
-- Erstellen eines Scheduler Jobs
procedure exec_sql(p_user in varchar2
                  ,p_sql  in varchar2
                  );
/
end my_utilities;

read more…

In: APEX, Oracle, PL/SQL, SQLAuthor: Sven BosingerComments (0)

One Filter to Rule them All: Dynamische Regeln für das Business

one filter to rule them allRules Manager und Expression Filter sind zwei sehr mächtige aber weitgehend unbekannte Bestandteile der Datenbank, die mit Oracle 10g eingeführt wurden.
Metadatengesteuert – und damit ohne Programmierung – lassen sich komplexe Regelwerke und Prüfungen erstellen.
Rulesmanager setzt auf Expression Filter auf. Durch den Einsatz dessen eigener Indextypen und SQL-Operatoren lassen sich auch sehr große Datenbestände performant bearbeiten.

Flexible Geschäftsregeln
Jedes Unternehmen hat Geschäftsregeln. IT-Systeme sind gute Gehilfen, wenn es darum geht, das Umsetzen dieser Regeln zu unterstützen und das Einhalten sicher zu stellen.

In den heutigen Zeiten verändern sich Regeln allerdings häufig so schnell, dass IT-Abteilungen mit den Anpassungen und Release-Zyklen nicht mehr nachkommen. Bis die Änderungen implementiert, getestet und abgenommen sind, haben sich die Anforderungen oftmals schon wieder verändert. Workarounds und Schatten-IT sind die Folge.

Diesen Teufelskreis kann man mit dem Einsatz von Rules Manager und Expression Filter durchbrechen.
Rules Manager ist eine generische Regel-Maschine, die Regeln über Metadaten bei Eintreffen von Ereignissen auslösen. Diese Metadaten können dann z.B. auch durch die Fachabteilung gepflegt werden, wenn ihr eine entsprechende Oberfläche zur Verfügung gestellt wird.
read more…

In: DOAG, Newsletter, Oracle, SQLAuthor: Robert MarzComments (0)

Partitionen einer Tabelle in Oracle auslesen

Für meinen ersten Block hier wollte ich erstmal ein einfaches Thema ansprechen:
Das Auslesen von vorhanden Partitionen einer Tabelle.

Mir passierte es, dass ich bei einem Kunden saß und mir mit Toad die Partitionen einer Tabelle anschauen wollte.
Also erstmal über die GUI die Tabelle im entsprechenden Schema suchen und sich dann die Description anzeigen lassen. Anschließend ganz einfach auf den Tab “Partitions” wechseln.
Eigentlich ganz einfach. Wenn nun aber die GUI Probleme hat wieder ins Leben zurückzukehren und aufzutauen, weil bereits so viele Partitionen existieren, hört die Einfachheit auf.

Was tut man also in so einer Situation?
Richtig! SQLPLUS öffnen und per Hand abfragen:

– Für den User:
SELECT table_name
, partition_name
, partition_position
, tablespace_name
FROM user_tab_partitions
WHERE TABLE_NAME = ‘&P_TABLE’
ORDER BY PARTITION_POSITION DESC;

– Mit Blick auf die dba_tab_partitions:
SELECT table_owner
, table_name
, partition_name
, partition_position
, tablespace_name
FROM dba_tab_partitions
WHERE TABLE_NAME = ‘&P_TABLE’
ORDER BY PARTITION_POSITION DESC;

In: Oracle, SQLAuthor: Sven BrömerComments (1)

“buffer busy waits” und “Next” Extent Größe

Das Warten auf “busy buffers” kann die unterschiedlichsten Ursachen haben. So werden 18 Block-Kassen unterschieden die sich nicht nur auf Daten-, Undo-, Sort- oder Index-Block (bzw. auf deren Header) beziehen, sondern auch bis hinunter auf Extent- und Datenfile-Ebene gehen.

Mein Problem

Nach dem Umzug meiner Datenbank von Solaris auf Linux mittels Datapump, zeigten die Statspack Reports  immer das Event “buffer busy waits” unter den Top-5. Die Anwendung auf der Instanz war unverändert; vielleicht ein verändertes Userverhalten (wo wir ja jetzt wesentlich flotter unterwegs waren). Mein erster Gedanke: “Hot Blocks”.

Die Analyse

Im Nachhinein gibt die v$session_wait leider nicht mehr viel her, also versuche ich es mit der v$session_wait_history und suche nach dem Event.

EVENT               SID       sum       sum  P1TEXT  P1  P2TEXT      P2  P3TEXT  P3
                        wait_time wait_cout
buffer busy waits   433         1         1  file#    7  block#  601840  class#   1
buffer busy waits   228   588         6  file#    7  block#       2  class#  13
buffer busy waits   513   588         6  file#    6  block#       2  class#  13

Ok. File# 7 ist das Daten Tablespace und 6 das Tablespace für die Indizes, aber was bitte schön ist die class# 13?

Recherchen ergeben: Class#13 steht für “file header block

Demnach handelt es sich hier nicht um die sonst üblichen konkurrierenden Zugriffe auf gleiche Datenblöcke, sondern spielt sich weiter unten auf der Datenfile Ebene ab.

Zudem wurden, dass kam dann so nebenbei heraus, zu den betroffenen Zeiten verstärkt Daten über die Anwendung in die Datenbank geladen.

Die Analyse der Datenfiles ergabt, dass beim initialen Anlegen der beiden Tablespases keine Größe für Next Extents angegeben wurde. Somit wurde ein Defaultwert von 8KB (was genau einem Block entspricht) angenommen.

Dementsprechend war das System,während der Ladevorgänge, ständig damit beschäftigt die Datenfiles um 8KB zu erweitern.

Die Lösung

Ich habe dann die Größe der Next Extents auf 100MB gesetzt und das Event “buffer busy waits”, zumindest was die Block-Klasse 13 betrifft, gehört der Vergangenheit an.

Abfrage der System Events

Mit folgendem SQL habe ich mir geholfen.

SELECT  nvl(s.schemaname, 'disconnected') as "Schema Name"
,       w.sid
,       w.event
,       e.wait_class
,       w.wait_time
,       w.wait_count
,       upper(w.p1text) as "P1 Type"
,       w.p1 as "P1 Value"
,       upper(w.p2text) as "P2 Type"
,       w.p2 as "P2 Value"
,       upper(w.p3text) as "P3 Type"
,       w.p3 as "P3 Value"
,       decode  (nvl(upper(w.p3text),'|')
                , 'CLASS#'
                , decode (w.p3
                         ,1 ,'data block'
                         ,2 ,'sort block'
                         ,3 ,'save undo block'
                         ,4 ,'segment header'
                         ,5 ,'save undo header'
                         ,6 ,'free list'
                         ,7 ,'extent map'
                         ,8 ,'1st level bmb'
                         ,9 ,'2nd level bmb'
                         ,10,'3rd level bmb'
                         ,11,'bitmap block'
                         ,12,'bitmap index block'
                         ,13,'file header block'
                         ,14,'unused'
                         ,15,'system undo header'
                         ,16,'system undo block'
                         ,17,'undo header'
                         ,18,'undo block'
                         )
                ,''                
                ) as "Block Type"
,       case when nvl(upper(w.p3text),'|') in ('CLASS#','BLOCKS','SET-ID#','BUF_PTR')
             then ( select  x.segment_name
                    from    dba_extents x
                    where   x.file_id = w.p1
                    and    w.p2 between x.block_id and x.block_id + x.blocks -1
                  )
        end as "Segment Type"
,       s.sql_hash_value                
FROM    (
         SELECT x.event
         ,      x.event#
         ,      x.sid
         ,      sum(x.wait_time) as wait_time
         ,      sum(x.wait_count) as wait_count
         ,      x.p1
         ,      x.p2
         ,      x.p3
         ,      x.p1text
         ,      x.p2text
         ,      x.p3text
         FROM   v$session_wait_history x
         WHERE  x.wait_time     > 0
         GROUP BY x.event, x.event#, x.sid, x.p1text, x.p2text, x.p3text, x.p1, x.p2, x.p3
        ) w
        ,       v$event_name            e
        ,       v$session               s
WHERE   e.event#        = w.event#
 AND    s.sid(+)        = w.sid
 AND    e.wait_class    not in ('Idle')
 AND    w.event         = 'buffer busy waits'
 AND    s.schemaname(+) like '%'||'&schema'||'%'
ORDER BY s.schemaname,w.sid;
In: PerformanceAuthor: Michael BrustComments (2)

SQL Zeilengenerator

Letzte Woche bin ich während eines Vortrages über folgenden Code-Schnipsel gestolpert:

select rownum ident
  from dba_objects
 where rownum <=500;

Mal eben ein paar Zeilen erzeugen ist eine Standardanforderung, der sich jeder SQL-Entwickler von Zeit zu Zeit stellen muss. Egal ob ein paar Demo-Zeilen erzeugt werden müssen oder für die Basis eines Outerjoins die vollständige Liste aller Tage in einem Zeitraum benötigt werden, Anwendungsmöglichkeiten gibt es viele.

Erschreckend häufig werden zu diesem Zweck Abfragen auf System-Views durchgeführt.

Schauen wir es uns einmal genauer an:
Dieses Statement liefert 500 Zeilen zurück. Das ist aber auch schon alles, was man ihm positives unterstellen kann.

Zunächst einmal wird hier auf eine DBA-View zugegriffen, auf die ein normales Schema ohne DBA-Berechtigungen gar keinen Zugriff hat. Das Verwenden von ALL_OBJECTS hilft dem zwar ab, aber kann man sich immer sicher sein, dass jeder Benutzer mehr als 500 Objekte im Zugriff hat?

Außerdem ist DBA_OBJECTS eine System-View, die auf verschiedene andere Views und Tabellen zugreift. Wenn man mal nachschaut, was man der Datenbank für diese einfache Aufgabe abverlangt, bekommt man das bestätigt:

select rownum id
  from dba_objects
 where rownum <=500;
-----------------------------------------------------------------------------------------------
| Id  | Operation                       | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                |             |   500 |       |     8  (13)| 00:00:01 |
|*  1 |  COUNT STOPKEY                  |             |       |       |            |          |
|   2 |   VIEW                          | DBA_OBJECTS |   500 |       |     8  (13)| 00:00:01 |
|   3 |    UNION-ALL                    |             |       |       |            |          |
|*  4 |     FILTER                      |             |       |       |            |          |
|*  5 |      HASH JOIN                  |             |   533 |   154K|     7  (15)| 00:00:01 |
|   6 |       INDEX FULL SCAN           | I_USER2     |    87 |   348 |     1   (0)| 00:00:01 |
|*  7 |       HASH JOIN                 |             |   533 |    97K|     6  (17)| 00:00:01 |
|   8 |        INDEX FULL SCAN          | I_USER2     |    87 |  1914 |     1   (0)| 00:00:01 |
|*  9 |        TABLE ACCESS FULL        | OBJ$        |   534 | 44322 |     4   (0)| 00:00:01 |
|* 10 |      TABLE ACCESS BY INDEX ROWID| IND$        |     1 |     8 |     2   (0)| 00:00:01 |
|* 11 |       INDEX UNIQUE SCAN         | I_IND1      |     1 |       |     1   (0)| 00:00:01 |
|  12 |      NESTED LOOPS               |             |     1 |    29 |     2   (0)| 00:00:01 |
|* 13 |       INDEX FULL SCAN           | I_USER2     |     1 |    20 |     1   (0)| 00:00:01 |
|* 14 |       INDEX RANGE SCAN          | I_OBJ4      |     1 |     9 |     1   (0)| 00:00:01 |
|  15 |     NESTED LOOPS                |             |     1 |    17 |     1   (0)| 00:00:01 |
|  16 |      INDEX FULL SCAN            | I_LINK1     |     1 |    13 |     0   (0)| 00:00:01 |
|* 17 |      INDEX RANGE SCAN           | I_USER2     |     1 |     4 |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------

Dabei liefert Oracle-SQL doch einen eigenen Generator mit:

select rownum id
  from dual
connect by rownum <= 500;

------------------------------------------------------------------------------
| Id  | Operation                     | Name | Rows  | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |      |     1 |     2   (0)| 00:00:01 |
|   1 |  COUNT                        |      |       |            |          |
|*  2 |   CONNECT BY WITHOUT FILTERING|      |       |            |          |
|   3 |    FAST DUAL                  |      |     1 |     2   (0)| 00:00:01 |
------------------------------------------------------------------------------

Diese wenig bekannte Anwendung der hierarchischen Abfrage ist schlank, schön und vor allem viel billiger.
Funktionieren tut das seit mindestens Oracle 9i – frühere Datenbankversionen zum Überprüfen habe ich leider zur Zeit nicht zur Hand.

Leider hat es die Oracle-Schreibweise für hierarchische Abfragen nicht in den ANSI-Standard geschafft, sodass man diese Art Abfragen in den meisten anderen Datenbanken nicht ausführen kann.

Für hierarchische Abfragen sieht ANSI SQL-99 rekursives “subquery refactoring with with-clause” vor.

Komplexe SQL-Abfragen durch with-Blöcke zu vereinfachen, beherrscht Oracle schon seit mindestens Oracle 9i. Die rekursive Aufruf-Erweiterung ist ein neues Feature der 11gR2, sodass man hierarchische Abfragen jetzt auch ANSI-konform formulieren kann:

with
 generator (id)
 as ( select 1 id from dual
      union all
      select id+1 from generator
      where id < 500 )
select id from generator;

--------------------------------------------------------------------------------------------------
| Id  | Operation                                 | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                          |      |     2 |    26 |     4   (0)| 00:00:01 |
|   1 |  VIEW                                     |      |     2 |    26 |     4   (0)| 00:00:01 |
|   2 |   UNION ALL (RECURSIVE WITH) BREADTH FIRST|      |       |       |            |          |
|   3 |    FAST DUAL                              |      |     1 |       |     2   (0)| 00:00:01 |
|*  4 |    RECURSIVE WITH PUMP                    |      |       |       |            |          |
--------------------------------------------------------------------------------------------------

Das ist fast so billig und beinahe genauso schön wie die Connect-by-Variante.

Durch das für die Rekursion nötige UNION ALL schränkt man bei dieser Variante auf “< 500″ ein, im Gegensatz zu den “<= 500″ bei dem Connect-by

In: Oracle, SQLAuthor: Robert MarzComments (11)