Skalierbare Anwendungen erstellen

Entwerfen Sie Ihre Anwendungen so, dass sie unabhängig von Benutzerpopulationen und Datenvolumen dieselben Ressourcen verwenden und keine Systemressourcen überlasten.

Skalierbare Anwendungen

Eine skalierbare Anwendung kann eine größere Workload verarbeiten, wobei die Systemressourcennutzung proportional erhöht wird.

Eine skalierbare Anwendung kann eine größere Workload verarbeiten, wobei die Systemressourcennutzung proportional erhöht wird. Beispiel: Wenn Sie die Arbeitslast verdoppeln, verwendet eine skalierbare Anwendung doppelt so viele Systemressourcen.

Eine nicht skalierbare Anwendung erschöpft eine Systemressource. Wenn Sie daher die Anwendungs-Workload erhöhen, ist kein weiterer Durchsatz möglich. Nicht skalierbare Anwendungen führen zu festen Durchsätzen und schlechten Reaktionszeiten.

Beispiele für die Ressourcenauslastung:

Entwerfen Sie Ihre Anwendungen so, dass sie unabhängig von Benutzerpopulationen und Datenvolumen dieselben Ressourcen verwenden und keine Systemressourcen überlasten.

Bindungsvariablen zur Verbesserung der Skalierbarkeit verwenden

Mit Bind-Variablen, die korrekt verwendet werden, können Sie effiziente, skalierbare Anwendungen entwickeln.

Eine Bind-Variable ist ein Platzhalter in einer SQL-Anweisung, der durch eine gültige Wert- oder Wertadresse ersetzt werden muss, damit die Anweisung erfolgreich ausgeführt wird. Mit Bind-Variablen können Sie eine SQL-Anweisung schreiben, die zur Laufzeit Eingaben oder Parameter akzeptiert.

Ein Unterprogramm kann Parameter aufweisen, deren Werte vom Aufrufer bereitgestellt werden. Eine SQL-Anweisung kann Platzhalter für Bind-Variablen aufweisen, deren Werte (sog. Bind-Variablen) zur Laufzeit bereitgestellt werden. So wie ein Unterprogramm einmal kompiliert und dann mehrmals mit verschiedenen Parametern ausgeführt wird, wird eine SQL-Anweisung mit Platzhalterzeichen für Bind-Variablen einmal hart geparst und dann mit verschiedenen Bind-Variablen weich geparst.

Ein Hard Parse, der Optimierung und Generierung von Zeilenquellen umfasst, ist ein sehr CPU-intensiver Vorgang. Ein Soft Parse, der die Optimierung und Generierung von Zeilenquellen überspringt und direkt zur Ausführung führt, ist in der Regel viel schneller als ein Hard Parse derselben Anweisung. (Ein Überblick über die SQL-Verarbeitung, die den Unterschied zwischen einem Hard Parse und einem Soft Parse beinhaltet, finden Sie unter Oracle Database-Konzepte.)

Ein CPU-intensiver Vorgang wird nicht nur hart geparst, sondern ist auch ein nicht skalierbarer Vorgang, da er nicht gleichzeitig mit vielen anderen Vorgängen ausgeführt werden kann. Weitere Informationen zu Nebenläufigkeit und Skalierbarkeit finden Sie unter "Nebenläufigkeit und Skalierbarkeit".

Beispiel 8-1 zeigt den Performanceunterschied zwischen einer Abfrage ohne Bind-Variable und einer semantisch äquivalenten Abfrage mit einer Bind-Variable. Ersteres ist langsamer und verwendet viele weitere Latches (Informationen dazu, wie sich Latches auf die Skalierbarkeit auswirken, finden Sie unter). Um Performancestatistiken zu sammeln und anzuzeigen, verwendet das Beispiel das Runstats-Tool, das unter "Vergleichen von Programmierungstechniken mit Runstats" beschrieben wird.

Hinweis:

Hinweis:

Obwohl Soft Parsing effizienter ist als Hard Parsing, sind die Kosten für Soft Parsing einer Anweisung um ein Vielfaches immer noch sehr hoch. Um die Effizienz und Skalierbarkeit Ihrer Anwendung zu maximieren, minimieren Sie das Parsen. Die einfachste Möglichkeit, das Parsen zu minimieren, ist die Verwendung von PL/SQL.

Beispiel 8-1: Bindungsvariable verbessert Performance

CREATE TABLE t ( x VARCHAR2(5) );

DECLARE
  TYPE rc IS REF CURSOR;
  l_cursor rc;
BEGIN
  runstats_pkg.rs_start;  -- Collect statistics for query without bind variable

  FOR i IN 1 .. 5000 LOOP
    OPEN l_cursor FOR 'SELECT x FROM t WHERE x = ' || TO_CHAR(i);
    CLOSE l_cursor;
  END LOOP;

  runstats_pkg.rs_middle;  -- Collect statistics for query with bind variable

  FOR i IN 1 .. 5000 LOOP
    OPEN l_cursor FOR 'SELECT x FROM t WHERE x = :x' USING i;
    CLOSE l_cursor;
  END LOOP;

  runstats_pkg.rs_stop(500);  -- Stop collecting statistics
end;
/

Das Ergebnis sieht etwa folgendermaßen aus:

Run 1 ran in 740 hsec
Run 2 ran in 30 hsec
Run 1 ran in 
2466.67% of the time of run 2

Name                                   Run 1         Run 2    Difference
STAT...recursive cpu usage               729            19          -710
STAT...CPU used by this sessio           742            30          -712
STAT...parse time elapsed              1,051             4        -1,047
STAT...parse time cpu                  1,066             2        -1,064
STAT...session cursor cache hi             1         4,998         4,997
STAT...table scans (short tabl         5,000             1        -4,999
STAT...parse count (total)            10,003         5,004        -4,999
LATCH.session idle bit                 5,003             3        -5,000
LATCH.session allocation               5,003             3        -5,000
STAT...execute count                  10,003         5,003        -5,000
STAT...opened cursors cumulati        10,003         5,003        -5,000
STAT...parse count (hard)             10,001             5        -9,996
STAT...CCursor + sql area evic        10,000             1        -9,999
STAT...enqueue releases               10,008             7       -10,001
STAT...enqueue requests               10,009             7       -10,002
STAT...calls to get snapshot s        20,005         5,006       -14,999
STAT...calls to kcmgcs                20,028            35       -19,993
STAT...consistent gets pin (fa        20,013            17       -19,996
LATCH.call allocation                 20,002             6       -19,996
STAT...consistent gets from ca        20,014            18       -19,996
STAT...consistent gets                20,014            18       -19,996
STAT...consistent gets pin            20,013            17       -19,996
LATCH.simulator hash latch            20,014            11       -20,003
STAT...session logical reads          20,080            75       -20,005
LATCH.shared pool simulator           20,046             5       -20,041
LATCH.enqueue hash chains             20,343            15       -20,328
STAT...recursive calls                40,015        15,018       -24,997
LATCH.cache buffers chains            40,480           294       -40,186
STAT...session pga memory max        131,072        65,536       -65,536
STAT...session pga memory            131,072        65,536       -65,536
LATCH.row cache objects              165,209           139      -165,070
STAT...session uga memory max        219,000             0      -219,000
LATCH.shared pool                    265,108           152      -264,956
STAT...logical read bytes from   164,495,360       614,400  -163,880,960

Run 1 latches total compared to run 2 -- difference and percentage
         Run 1         Run 2          Diff       Pct
       562,092           864      -561,228  2,466.67%

PL/SQL procedure successfully completed.

Skalierbarkeit mit PL/SQL verbessern

Bestimmte PL/SQL-Features können Ihnen helfen, die Skalierbarkeit von Anwendungen zu verbessern.

Parsing durch PL/SQL minimieren

PL/SQL, das für den Datenbankzugriff optimiert ist, speichert Anweisungen automatisch im Cache. Wenn Sie einen Cursor in PL/SQL schließen, wird der Cursor aus Ihrer Perspektive geschlossen. Das heißt, Sie können ihn nicht dort verwenden, wo ein offener Cursor erforderlich ist. PL/SQL hält den Cursor jedoch tatsächlich offen und speichert seine Anweisung im Cache.

Wenn Sie die gecachte Anweisung erneut verwenden, verwendet PL/SQL denselben Cursor, wodurch ein Parse vermieden wird. (PL/SQL schließt gecachte Anweisungen, falls erforderlich. Beispiel: Wenn das Programm einen anderen Cursor öffnen muss, dies jedoch die init.ora-Einstellung von OPEN_CURSORS überschreitet.)

PL/SQL kann nur SQL-Anweisungen im Cache speichern, die zur Laufzeit nicht geändert werden können.

Info zur Anweisung EXECUTE IMMEDIATE

Die EXECUTE IMMEDIATE-Anweisung erstellt und führt eine dynamische SQL-Anweisung in einem einzigen Vorgang aus.

Die allgemeine Syntax der EXECUTE IMMEDIATE-Anweisung lautet:

EXECUTE IMMEDIATE sql_statement

sql_statement ist eine Zeichenfolge, die eine SQL-Anweisung darstellt. Wenn sql_statement bei jeder Ausführung der EXECUTE IMMEDIATE-Anweisung denselben Wert aufweist, kann PL/SQL die EXECUTE IMMEDIATE-Anweisung cachen. Wenn sql_statement bei jeder Ausführung der EXECUTE IMMEDIATE-Anweisung unterschiedlich sein kann, kann PL/SQL die EXECUTE IMMEDIATE-Anweisung nicht cachen.

Siehe:

Informationen zu OPEN FOR-Anweisungen

Die OPEN FOR-Anweisung weist die folgende grundlegende Syntax auf:

Die allgemeine Syntax der OPEN FOR-Anweisung lautet:

OPEN cursor_variable FOR query

Ihre Anwendung kann cursor_variable für mehrere verschiedene Abfragen öffnen, bevor Sie sie schließen. Da PL/SQL die Anzahl verschiedener Abfragen erst zur Laufzeit bestimmen kann, kann PL/SQL die OPEN FOR-Anweisung nicht cachen.

Wenn Sie keine Cursorvariable verwenden müssen, verwenden Sie einen deklarierten Cursor, um die Performance zu verbessern und die Programmierung zu vereinfachen. Weitere Informationen finden Sie im Oracle Database Development Guide.

Siehe:

DBMS_SQL-Package

Das DBMS_SQL-Package ist eine API zum Erstellen, Ausführen und Beschreiben dynamischer SQL-Anweisungen. Sie müssen das DBMS_SQL-Package anstelle der EXECUTE IMMEDIATE-Anweisung verwenden, wenn der PL/SQL-Compiler bei der Kompilierung die Anzahl oder Typen von Ausgabehostvariablen (Listenelemente auswählen) oder Eingabe-Bind-Variablen nicht bestimmen kann.

Das DBMS_SQL-Package ist eine API zum Erstellen, Ausführen und Beschreiben dynamischer SQL-Anweisungen. Die Verwendung des DBMS_SQL-Packages erfordert mehr Aufwand als die Verwendung der Anweisung EXECUTE IMMEDIATE. Sie müssen jedoch das DBMS_SQL-Package verwenden, wenn der PL/SQL-Compiler bei der Kompilierung die Anzahl oder Typen von Ausgabehostvariablen (Listenelemente auswählen) oder Eingabe-Bind-Variablen nicht bestimmen kann.

Siehe:

Informationen zu Bulk SQL

Bulk SQL reduziert die Anzahl der "Roundtrips" zwischen PL/SQL und SQL, wodurch weniger Ressourcen verwendet werden.

Ohne Bulk-SQL rufen Sie jeweils eine Zeile aus der Datenbank (SQL) ab, verarbeiten sie (PL/SQL) und geben sie an die Datenbank (SQL) zurück. Mit Bulk SQL rufen Sie eine Gruppe von Zeilen aus der Datenbank ab, verarbeiten die Zeilengruppe und geben dann die gesamte Gruppe an die Datenbank zurück.

Oracle empfiehlt die Verwendung von Bulk SQL, wenn Sie mehrere Zeilen aus der Datenbank abrufen und sie an die Datenbank zurückgeben, wie in Beispiel 8-2. Sie benötigen kein Bulk-SQL, wenn Sie mehrere Zeilen abrufen, sie jedoch nicht zurückgeben. Beispiel:

FOR x IN (SELECT * FROM t WHERE ... )  -- Retrieve row set (implicit array fetch)
  LOOP
    DBMS_OUTPUT.PUT_LINE(t.x);          -- Process rows but do not return them
  END LOOP;

Beispiel 8-2 durchläuft eine Tabelle t mit einem Spaltenobjektnamen, ruft 100 Zeilen ab, verarbeitet sie und gibt sie an die Datenbank zurück. (Um die Bulk-FETCH-Anweisung auf 100 Zeilen zu beschränken, ist ein expliziter Cursor erforderlich.)

In Beispiel 8-3 wird derselbe Job wie in Beispiel 8-2 ohne Bulk-SQL ausgeführt.

Wie diese TKPROF-Berichte für Beispiel 8-2 und Beispiel 8-3 zeigen, verbraucht die Verwendung von Bulk-SQL für diesen Job fast 50% weniger CPU-Zeit:

SELECT ROWID RID, OBJECT_NAME FROM T T_BULK

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      721      0.17       0.17          0      22582          0       71825
********************************************************************************
UPDATE T SET OBJECT_NAME = :B1 WHERE ROWID = :B2

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute    719     12.83      13.77          0      71853      74185       71825
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      720     12.83      13.77          0      71853      74185       71825


SELECT ROWID RID, OBJECT_NAME FROM T T_SLOW_BY_SLOW

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      721      0.17       0.17          0      22582          0       71825
********************************************************************************
UPDATE T SET OBJECT_NAME = :B2 WHERE ROWID = :B1

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute  71824     21.25      22.25          0      71836      73950       71824
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total    71825     21.25      22.25          0      71836      73950       71824

Die Verwendung von Bulk-SQL für diesen Job verwendet jedoch mehr CPU-Zeit und mehr Code als eine einzelne SQL-Anweisung, wie der folgende TKPROF-Bericht zeigt:

UPDATE T SET OBJECT_NAME = SUBSTR(OBJECT_NAME,2) || SUBSTR(OBJECT_NAME,1,1)

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      1.30       1.44          0       2166      75736       71825
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        2      1.30       1.44          0       2166      75736       71825

Beispiel 8-2: Bulk-SQL

CREATE OR REPLACE PROCEDURE bulk AS
  TYPE ridArray IS TABLE OF ROWID;
  TYPE onameArray IS TABLE OF t.object_name%TYPE;

  CURSOR c is SELECT ROWID rid, object_name  -- explicit cursor
              FROM t t_bulk;

  l_rids    ridArray;
  l_onames  onameArray;
  N         NUMBER := 100;
BEGIN
  OPEN c;
  LOOP
    FETCH c BULK COLLECT
    INTO l_rids, l_onames LIMIT N;   -- retrieve N rows from t

    FOR i in 1 .. l_rids.COUNT
      LOOP                           -- process N rows
        l_onames(i) := substr(l_onames(i),2) || substr(l_onames(i),1,1);
      END LOOP;

      FORALL i in 1 .. l_rids.count  -- return processed rows to t
        UPDATE t
        SET object_name = l_onames(i)
        WHERE ROWID = l_rids(i);
        EXIT WHEN c%NOTFOUND;
  END LOOP;
  CLOSE c;
END;
/

Beispiel 8-3 ohne Bulk SQL

CREATE OR REPLACE PROCEDURE slow_by_slow AS
BEGIN
  FOR x IN (SELECT rowid rid, object_name FROM t t_slow_by_slow)
    LOOP
      x.object_name := substr(x.object_name,2) || substr(x.object_name,1,1);

      UPDATE t
      SET object_name = x.object_name
      WHERE rowid = x.rid;
    END LOOP;
END;

Siehe:

Nebenläufigkeit und Skalierbarkeit

Nebenläufigkeit ist die gleichzeitige Ausführung mehrerer Transaktionen. Je besser Ihre Anwendung mit gleichzeitigem Zugriff umgeht, desto skalierbarer ist sie. Eine skalierbare Anwendung kann eine größere Workload verarbeiten, wobei die Systemressourcennutzung proportional erhöht wird.

Nebenläufigkeit ist die gleichzeitige Ausführung mehrerer Transaktionen. Anweisungen in gleichzeitigen Transaktionen können dieselben Daten aktualisieren. Je besser Ihre Anwendung mit gleichzeitigem Zugriff umgeht, desto skalierbarer ist sie. Eine skalierbare Anwendung kann eine größere Workload verarbeiten, wobei die Systemressourcennutzung proportional erhöht wird. Beispiel: Wenn Sie die Arbeitslast verdoppeln, verwendet eine skalierbare Anwendung doppelt so viele Systemressourcen.

Gleichzeitige Transaktionen müssen aussagekräftige und konsistente Ergebnisse liefern. Daher muss eine Mehrbenutzerdatenbank Folgendes bereitstellen:

Oracle Database sorgt für Datenkonsistenz, indem ein Konsistenzmodell mit mehreren Versionen und verschiedene Arten von Sperren und Transaktionsisolationsebenen verwendet werden. Einen Überblick über den Sperrmechanismus von Oracle Database finden Sie in Oracle Database Concepts. Einen Überblick über die Isolationsebenen von Oracle Database-Transaktionen finden Sie unter Oracle Database-Konzepte.

Um ein konsistentes Transaktionsverhalten zu beschreiben, wenn Transaktionen gleichzeitig ausgeführt werden, haben Datenbankforscher eine Transaktionsisolationskategorie definiert, die als serialisierbar bezeichnet wird. Eine serialisierbare Transaktion wird in einer Umgebung ausgeführt, die scheinbar eine Einzelbenutzerdatenbank ist. Serialisierbare Transaktionen sind in bestimmten Fällen wünschenswert, aber für 99% der Arbeitslast ist die Isolierung von Read Committed perfekt.

Oracle Database verfügt über Features, die den gleichzeitigen Zugriff und die Skalierbarkeit verbessern, z.B. Sequences, Latches, nicht blockierende Lese- und Schreibvorgänge und Shared SQL.

Siehe auch: Oracle Database-Konzepte für weitere Informationen zu gleichzeitigem Datenzugriff und -konsistenz

Sequences und Nebenläufigkeit

Sequences eliminieren die Serialisierung und verbessern so den gleichzeitigen Zugriff und die Skalierbarkeit Ihrer Anwendung.

Eine Sequenz ist ein Schemaobjekt, aus dem mehrere Benutzer eindeutige Ganzzahlen generieren können. Dies ist sehr nützlich, wenn Sie eindeutige Primärschlüssel benötigen.

Ohne Sequenzen müssen eindeutige Primärschlüsselwerte programmgesteuert erzeugt werden. Ein Benutzer erhält einen neuen Primärschlüsselwert, indem er den zuletzt erzeugten Wert auswählt und inkrementiert. Diese Technik erfordert eine Sperre während der Transaktion und führt dazu, dass mehrere Benutzer auf den nächsten Primärschlüsselwert warten, d.h. die Transaktionen serialisieren. Sequences eliminieren die Serialisierung und verbessern so den gleichzeitigen Zugriff und die Skalierbarkeit Ihrer Anwendung.

Siehe:

Latches und Nebenläufigkeit

Eine Erhöhung der Latches bedeutet mehr nebenläufigkeitsbasierte Waits und damit eine Verringerung der Skalierbarkeit.

Ein Latch ist ein einfacher Serialisierungsmechanismus auf niedriger Ebene, der den Mehrbenutzerzugriff auf gemeinsame Datenstrukturen koordiniert. Latches schützen Shared Memory-Ressourcen vor Beschädigung, wenn mehrere Prozesse auf sie zugreifen.

Eine Erhöhung der Latches bedeutet mehr nebenläufigkeitsbasierte Waits und damit eine Verringerung der Skalierbarkeit. Wenn Sie einen Ansatz verwenden können, der während der Entwicklung etwas schneller ausgeführt wird, oder einen Ansatz, der weniger Latches verwendet, verwenden Sie diesen Ansatz.

Siehe:

Nicht blockierende Lese- und Schreibvorgänge sowie Nebenläufigkeit

In Oracle Database können nicht blockierende Lese- und Schreibvorgänge Abfragen gleichzeitig mit Änderungen an den Daten ausführen, die sie lesen, ohne zu blockieren oder zu stoppen. Durch nicht blockierende Lese- und Schreibvorgänge kann eine Session Daten lesen, während eine andere Session diese Daten ändert.

Shared SQL und Nebenläufigkeit

Oracle Database kompiliert eine SQL-Anweisung einmal in ein ausführbares Objekt, und dann können andere Sessions das Objekt wiederverwenden, solange es vorhanden ist. Mit diesem Oracle Database-Feature, das als Shared SQL bezeichnet wird, kann die Datenbank sehr ressourcenintensive Vorgänge kompilieren und SQL-Anweisungen nur einmal optimieren, anstatt jedes Mal, wenn eine Session dieselbe SQL-Anweisung verwendet.

Siehe auch: Oracle Database-Konzepte für weitere Informationen zu Shared SQL

Anzahl gleichzeitiger Sessions begrenzen

Je mehr gleichzeitige Sessions Sie haben, desto mehr nebenläufigkeitsbasierte Waits haben Sie und desto langsamer ist Ihre Antwortzeit.

Wenn Ihr Computer über n CPU-Cores verfügt, können maximal n Sessions gleichzeitig aktiv sein. Jede weitere "gleichzeitige" Session muss warten, bis ein CPU-Core verfügbar ist, bevor er aktiv werden kann. Wenn einige wartende Sessions nur auf I/O warten, kann eine Erhöhung der Anzahl gleichzeitiger Sessions auf etwas mehr als n die Laufzeitperformance etwas verbessern. Wenn Sie jedoch die Anzahl gleichzeitiger Sessions zu stark erhöhen, wird die Laufzeitperformance erheblich reduziert.

Der Initialisierungsparameter SESSIONS bestimmt die maximale Anzahl gleichzeitiger Benutzer im System. Weitere Informationen finden Sie in der Oracle Database-Referenz.

Siehe auch: http://www.youtube.com/watch?v=xNDnVOCdvQ0 für ein Video, das zeigt, wie sich die Anzahl gleichzeitiger Sessions auf einem Computer mit 12 CPU-Kernen von Tausenden auf 96 reduzieren lässt

Vergleich von Programmierungstechniken mit Runstats

Mit dem Runstats-Tool können Sie die Leistung von zwei Programmierungstechniken vergleichen, um zu sehen, welche besser ist.

Über Runstats

Mit dem Runstats-Tool können Sie die Leistung von zwei Programmierungstechniken vergleichen, um zu sehen, welche besser ist.

Runstats misst die folgenden Werte:

Von den vorhergehenden Messungen ist das Verriegeln am wichtigsten (siehe "Info zu Latches und Nebenläufigkeit").

Siehe auch: Beispiel 8-1, das Runstats verwendet

Runstats einrichten

Das Runstats-Tool wird als Paket implementiert, das eine View und eine temporäre Tabelle verwendet.

Hinweis: Für Schritt 1 der folgenden Prozedur benötigen Sie die SELECT-Berechtigung für die dynamischen Performanceansichten V$STATNAME, V$MYSTAT und V$LATCH. Wenn Sie diese Berechtigung nicht erhalten können, erstellen Sie die Ansicht in Schritt 1, und erteilen Sie Ihnen die Berechtigung SELECT.

Schritte zum Einrichten des Runstats-Tools:

  1. Erstellen Sie die Ansicht, die Runstats verwendet:

     CREATE OR REPLACE VIEW stats
     AS SELECT 'STAT...' || a.name name, b.value
     FROM V$STATNAME a, V$MYSTAT b
     WHERE a.statistic# = b.statistic#
     UNION ALL
     SELECT 'LATCH.' || name, gets
     FROM V$LATCH;
    
  2. Erstellen Sie die temporäre Tabelle, die Runstats verwendet:

     DROP TABLE run_stats;
    
     CREATE GLOBAL TEMPORARY TABLE run_stats
     ( runid VARCHAR2(15),
       name VARCHAR2(80),
       value INT )
     ON COMMIT PRESERVE ROWS;
    
  3. Erstellen Sie die Package-Spezifikation:

     CREATE OR REPLACE PACKAGE runstats_pkg
     AS
       PROCEDURE rs_start;
       PROCEDURE rs_middle;
       PROCEDURE rs_stop( p_difference_threshold IN NUMBER DEFAULT 0 );
     end;
     /
    

    Der Parameter p_difference_threshold steuert die Anzahl der Statistiken und Verriegelungsdaten, die Runstats anzeigt. Runstats zeigt Daten nur an, wenn der Unterschied für die beiden Techniken größer als p_difference_threshold ist. Standardmäßig zeigt Runstats alle Daten an.

  4. Erstellen Sie den folgenden Packagebody:

     CREATE OR REPLACE PACKAGE BODY runstats_pkg
     AS
       g_start NUMBER;
       g_run1 NUMBER;
       g_run2 NUMBER;
    
       PROCEDURE rs_start
       IS
       BEGIN
         DELETE FROM run_stats;
    
         INSERT INTO run_stats
         SELECT 'before', stats.* FROM stats;
    
         g_start := DBMS_UTILITY.GET_TIME;
       END rs_start;
    
       PROCEDURE rs_middle
       IS
       BEGIN
         g_run1 := (DBMS_UTILITY.GET_TIME - g_start);
    
         INSERT INTO run_stats
         SELECT 'after 1', stats.* FROM stats;
    
         g_start := DBMS_UTILITY.GET_TIME;
       END rs_middle;
    
       PROCEDURE rs_stop( p_difference_threshold IN NUMBER DEFAULT 0 )
       IS
       BEGIN
         g_run2 := (DBMS_UTILITY.GET_TIME - g_start);
    
         DBMS_OUTPUT.PUT_LINE
           ('Run 1 ran in ' || g_run1 || ' hsec');
    
         DBMS_OUTPUT.PUT_LINE
           ('Run 2 ran in ' || g_run2 || ' hsec');
    
         DBMS_OUTPUT.PUT_LINE
           ('Run 1 ran in ' || round(g_run1/g_run2*100, 2) || '% of the time of run 2');
    
         DBMS_OUTPUT.PUT_LINE( CHR(9) );
    
         INSERT INTO run_stats
         SELECT 'after 2', stats.* FROM stats;
    
         DBMS_OUTPUT.PUT_LINE
           ( RPAD( 'Name', 30 ) ||
             LPAD( 'Run 1', 14) ||
    
             LPAD( 'Run 2', 14) ||
    
             LPAD( 'Difference', 14)
           );
    
         FOR x IN
         ( SELECT RPAD( a.name, 30 ) ||
                 TO_CHAR( b.value - a.value, '9,999,999,999' ) ||
                 TO_CHAR( c.value - b.value, '9,999,999,999' ) ||
                 TO_CHAR( ( (c.value - b.value) - (b.value - a.value)),
                   '9,999,999,999' ) data
           FROM run_stats a, run_stats b, run_stats c
           WHERE a.name = b.name
             AND b.name = c.name
             AND a.runid = 'before'
             AND b.runid = 'after 1'
             AND c.runid = 'after 2'
             AND (c.value - a.value) > 0
             AND abs((c.value - b.value) - (b.value - a.value)) >
               p_difference_threshold
         ORDER BY ABS((c.value - b.value) - (b.value - a.value))
         ) LOOP
             DBMS_OUTPUT.PUT_LINE( x.data );
         END LOOP;
    
         DBMS_OUTPUT.PUT_LINE( CHR(9) );
    
         DBMS_OUTPUT.PUT_LINE(
           'Run 1 latches total compared to run 2 -- difference and percentage' );
    
         DBMS_OUTPUT.PUT_LINE
           ( LPAD( 'Run 1', 14) ||
    
             LPAD( 'Run 2', 14) ||
    
             LPAD( 'Diff', 14) ||
    
             LPAD( 'Pct', 10)
           );
    
         FOR x IN
         ( SELECT TO_CHAR( run1, '9,999,999,999' ) ||
                 TO_CHAR( run2, '9,999,999,999' ) ||
                 TO_CHAR( diff, '9,999,999,999' ) ||
                 TO_CHAR( ROUND( g_run1/g_run2*100, 2), '99,999.99' ) || '%' data
           FROM ( SELECT SUM (b.value - a.value) run1,
                         SUM (c.value - b.value) run2,
                         SUM ( (c.value - b.value) - (b.value - a.value)) diff
                 FROM run_stats a, run_stats b, run_stats c
                 WHERE a.name = b.name
                   AND b.name = c.name
                   AND a.runid = 'before'
                   AND b.runid = 'after 1'
                   AND c.runid = 'after 2'
                   AND a.name like 'LATCH%'
               )
         ) LOOP
             DBMS_OUTPUT.PUT_LINE( x.data );
         END LOOP;
    
       END rs_stop;
    
     END;
     /
    

Siehe:

Runstats verwenden

In diesem Thema wird die Syntax für die Verwendung des Runstats-Tools beschrieben.

Um mit Runstats zwei Programmiertechniken zu vergleichen, rufen Sie die runstats_pkg-Prozeduren aus einem anonymen Block mit der folgenden Syntax auf:

[ DECLARE local_declarations ]
BEGIN
  runstats_pkg.rs_start;
  code_for_first_technique
  runstats_pkg.rs_middle;
  code_for_second_technique
  runstats_pkg.rs_stop(n);
END;
/

Siehe auch: Beispiel 8-1, das Runstats verwendet

Real-World Performance und Datenverarbeitungstechniken

Eine häufige Aufgabe in Datenbankanwendungen in einer Data Warehouse-Umgebung besteht darin, ein großes Dataset abzufragen oder zu ändern. Das Problem für Anwendungsentwickler besteht darin, bei der Verarbeitung großer Datasets eine hohe Performance zu erreichen.

Verarbeitungstechniken lassen sich in zwei Kategorien einteilen: iterativ und setbasiert. Im Laufe der Jahre hat die Real-World Performance-Gruppe festgestellt, dass setbasierte Verarbeitungstechniken Größenordnungen besser ausführen für Datenbankanwendungen, die große Datasets verarbeiten.

Dieses Thema umfasst die folgenden Hauptunterthemen:

Informationen zur iterativen Datenverarbeitung

Bei der iterativen Verarbeitung verwenden Anwendungen bedingte Logik, um eine Reihe von Zeilen zu durchlaufen.

Normalerweise verwendet die iterative Verarbeitung, wenn auch nicht unbedingt, wie folgt ein Client/Server-Modell:

  1. Übertragen Sie eine Gruppe von Zeilen vom Datenbankserver an die Client-Anwendung.

  2. Verarbeiten Sie die Gruppe innerhalb der Clientanwendung.

  3. Übertragen Sie die verarbeitete Gruppe wieder auf den Datenbankserver.

Sie können iterative Algorithmen mit drei Haupttechniken implementieren: Row-by-Row-Verarbeitung, Array-Verarbeitung und manuelle Parallelisierung.

Iterative Verarbeitung: Zeile für Zeile

Bei der Row-by-Row-Verarbeitung durchläuft ein einzelner Prozess ein Dataset und wird jeweils für eine einzelne Zeile ausgeführt. In einer typischen Implementierung ruft die Anwendung jede Zeile aus der Datenbank ab, verarbeitet sie in der Middle Tier und sendet die Zeile dann zurück an die Datenbank, die DML und Commits ausführt.

Angenommen, Ihre funktionale Anforderung besteht darin, eine externe Tabelle namens ext_scan_events abzufragen und die zugehörigen Zeilen dann in eine Heap-organisierte Staging-Tabelle namens stage1_scan_events einzufügen. Der folgende PL/SQL-Block verwendet eine Row-by-Row-Technik, um diese Anforderung zu erfüllen:

declare
  cursor c is select s.* from ext_scan_events s;
  r c%rowtype;
begin
  open c;
  loop
    fetch c into r;
    exit when c%notfound;
    insert into stage1_scan_events d values r;
    commit;
  end loop;
  close c;
end;

Die Row-by-Row-Technik hat folgende Vorteile:

Die Row-by-Row-Technik hat folgende Nachteile:

Siehe auch: RWP #7 Set-Based Processing

Iterative Verarbeitung: Arrays

Die Array-Verarbeitung ist mit der zeilenweisen Verarbeitung identisch. Der Unterschied besteht darin, dass sie eine Gruppe von Datensätzen in jeder Iteration und nicht einen einzelnen Datensatz verarbeitet.

Beispiel X-X: Fragen Sie eine externe Tabelle mit dem Namen ext_scan_events ab, und fügen Sie die zugehörigen Zeilen in eine Heap-organisierte Staging-Tabelle mit dem Namen stage1_scan_events ein. Der folgende PL/SQL-Block verwendet eine Array-Technik, um diese Anforderung zu erfüllen:

declare
  cursor c is select s.* from ext_scan_events s;
  type t is table of c%rowtype index by binary_integer;
  a t;
  rows binary_integer := 0;
begin
  open c;
  loop
    fetch c bulk collect into a limit array_size;
    exit when a.count = 0;
    forall i in 1..a.count
      insert into stage1_scan_events d values a(i);
    commit;
  end loop;
  close c;
end;

Der vorhergehende Code unterscheidet sich vom äquivalenten Row-by-Row-Code bei der Verwendung eines BULK COLLECT-Operators in der FETCH STATEMENT, der durch den Wert array_size des Typs PLS_INTEGER begrenzt ist. Beispiel: Wenn array_size auf 100 gesetzt ist, ruft die Anwendung Zeilen in Gruppen von 100 ab.

Die Array-Technik hat gegenüber der Row-by-Row-Technik folgende Vorteile:

Die Nachteile dieser Technik sind die gleichen wie bei der Row-by-Row-Verarbeitung. Die Verarbeitungszeit kann für große Datasets nicht akzeptabel sein. Außerdem muss die Anwendung seriell auf einem einzelnen CPU-Core ausgeführt werden, sodass die native Parallelität von Oracle Database nicht ausgenutzt werden kann.

Iterative Verarbeitung: Manuelle Parallelität

Bei der manuellen Parallelisierung wird der gleiche iterative Algorithmus wie bei der Zeilen- und Arrayverarbeitung verwendet. Mehrere Serverprozesse können jedoch die Arbeit aufteilen und parallel ausführen.

Angenommen, die funktionale Anforderung ist mit den Beispielen für Zeilen und Arrays identisch. Die Hauptunterschiede sind:

Der folgende PL/SQL-Code verwendet manuelle Parallellismus:

declare
  sqlstmt varchar2(1024) := q'[
-- BEGIN embedded anonymous block
  cursor c is select s.* from ext_scan_events_${thr} s;
  type t is table of c%rowtype index by binary_integer;
  a t;
  rows binary_integer := 0;
begin
  for r in (select ext_file_name from ext_scan_events_dets where ora_hash(file_seq_nbr,${thrs}) = ${thr})
  loop
    execute immediate
      'alter table ext_scan_events_${thr} location' || '(' || r.ext_file_name || ')';
    open c;
    loop
      fetch c bulk collect into a limit ${array_size};
      exit when a.count = 0;
      forall i in 1..a.count
        insert into stage1_scan_events d values a(i);
      commit;
--  demo instrumentation
      rows := rows + a.count; if rows > 1e3 then exit when not sd_control.p_progress('loading','userdefined',rows); rows := 0; end if;
    end loop;
    close c;
  end loop;
end;
-- END   embedded anonymous block
]';

begin
  sqlstmt := replace(sqlstmt, '${array_size}', to_char(array_size));
  sqlstmt := replace(sqlstmt, '${thr}', thr);
  sqlstmt := replace(sqlstmt, '${thrs}', thrs);
  execute immediate sqlstmt;
end;

Die Funktion ORA_HASH teilt die Tabelle ext_scan_events_dets in 32 gleichmäßig verteilte Buckets auf. Anschließend ruft die SELECT-Anweisung die Dateinamen für den Bucket 0 ab. Für jeden Dateinamen im Bucket setzt das Programm den Speicherort der externen Tabelle auf diesen Dateinamen. Anschließend fragt das Programm die externe Tabelle mithilfe der Batch-Verarbeitung ab, fügt sie in die Staging-Tabelle ein, und schreibt sie fest.

Während Job 1 ausgeführt wird, werden die anderen 31 Oracle Scheduler-Jobs parallel ausgeführt. Auf diese Weise liest jeder Job gleichzeitig eine andere Teilmenge der Scanereignisdateien und fügt die Datensätze aus ihrer Teilmenge in dieselbe Staging-Tabelle ein.

Die manuelle Parallelisierungstechnik hat gegenüber den alternativen iterativen Techniken folgende Vorteile:

Die manuelle Parallelisierungstechnik hat folgende Nachteile:

Siehe auch: RWP #8: Set-Based Parallel Processing

Set-basierte Verarbeitung

Die setbasierte Verarbeitung ist eine SQL-Technik, die ein Dataset innerhalb der Datenbank verarbeitet.

In einem setbasierten Modell definiert die SQL-Anweisung das Ergebnis und ermöglicht es der Datenbank, die effizienteste Methode zu ermitteln, um es zu erhalten. Im Gegensatz dazu verwenden iterative Algorithmen bedingte Logik, um jede Zeile oder Gruppe von Zeilen aus der Datenbank in die Clientanwendung abzurufen, die Daten auf dem Client zu verarbeiten und dann die Daten zurück an die Datenbank zu senden. Set-basierte Verarbeitung eliminiert den Overhead von Netzwerk-Roundtrip und Datenbank-API, da die Daten die Datenbank nie verlassen.

Angenommen, es gelten dieselben funktionalen Anforderungen wie in den vorherigen Beispielen. Die folgenden SQL-Anweisungen erfüllen diese Anforderung mit einem setbasierten Algorithmus:

alter session enable parallel dml;
insert /*+ APPEND */ into stage1_scan_events d
  select s.* from ext_scan_events s;
commit;

Da die INSERT-Anweisung eine Unterabfrage der Tabelle ext_scan_events enthält, liest und schreibt eine einzelne SQL-Anweisung alle Zeilen. Außerdem führt die Anwendung einen einzelnen COMMIT aus, nachdem die Datenbank alle Zeilen eingefügt hat. Iterative Anwendungen hingegen führen nach dem Einfügen jeder Zeile oder jeder Gruppe von Zeilen ein COMMIT aus.

Die satzbasierte Technik hat erhebliche Vorteile gegenüber iterativen Techniken:

Set-basierte Verarbeitung hat einige potenzielle Nachteile:

Siehe auch: RWP #7 Set-Based Processing, RWP #8: Set-Based Parallel Processing, RWP #9: Set-Based Processing–Data Deduplication, RWP #10: Set-Based Processing–Data Transformations und RWP #11: Set-Based Processing–Data Aggregation