Oracle
 sql >> Datenbank >  >> RDS >> Oracle

Oracle:Massenerfassungsleistung

Innerhalb von Oracle gibt es eine virtuelle SQL-Maschine (VM) und eine PL/SQL-VM. Wenn Sie von einer VM zu einer anderen VM wechseln müssen, entstehen Ihnen die Kosten für eine Kontextverschiebung. Einzeln sind diese Kontextwechsel relativ schnell, aber wenn Sie eine zeilenweise Verarbeitung durchführen, können sie einen erheblichen Teil der Zeit ausmachen, die Ihr Code verbringt. Wenn Sie Massenbindungen verwenden, verschieben Sie mehrere Datenzeilen mit einem einzigen Kontextwechsel von einer VM auf die andere, wodurch die Anzahl der Kontextwechsel erheblich reduziert und Ihr Code schneller wird.

Nehmen Sie zum Beispiel einen expliziten Cursor. Wenn ich so etwas schreibe

DECLARE
  CURSOR c 
      IS SELECT *
           FROM source_table;
  l_rec source_table%rowtype;
BEGIN
  OPEN c;
  LOOP
    FETCH c INTO l_rec;
    EXIT WHEN c%notfound;

    INSERT INTO dest_table( col1, col2, ... , colN )
      VALUES( l_rec.col1, l_rec.col2, ... , l_rec.colN );
  END LOOP;
END;

dann jedes Mal, wenn ich den Abruf ausführe, bin ich

  • Durchführen eines Kontextwechsels von der PL/SQL-VM zur SQL-VM
  • Die SQL-VM auffordern, den Cursor auszuführen, um die nächste Datenzeile zu generieren
  • Durchführen einer weiteren Kontextverschiebung von der SQL-VM zurück zur PL/SQL-VM, um meine einzelne Datenzeile zurückzugeben

Und jedes Mal, wenn ich eine Zeile einfüge, mache ich dasselbe. Mir entstehen die Kosten einer Kontextverschiebung, um eine Datenzeile von der PL/SQL-VM an die SQL-VM zu senden und die SQL aufzufordern, INSERT auszuführen -Anweisung und dann die Kosten für einen weiteren Kontextwechsel zurück zu PL/SQL tragen.

Wenn source_table hat 1 Million Zeilen, das sind 4 Millionen Kontextverschiebungen, die wahrscheinlich einen angemessenen Bruchteil der verstrichenen Zeit meines Codes ausmachen. Wenn ich dagegen eine BULK COLLECT durchführe mit einem LIMIT von 100 kann ich 99 % meiner Kontextverschiebungen eliminieren, indem ich jedes Mal, wenn mir die Kosten einer Kontextverschiebung entstehen, 100 Datenzeilen aus der SQL-VM in eine Sammlung in PL/SQL abrufe und jedes Mal 100 Zeilen in die Zieltabelle einfüge dort eine Kontextverschiebung erleiden.

Wenn ich meinen Code umschreiben kann, um Massenoperationen zu verwenden

DECLARE
  CURSOR c 
      IS SELECT *
           FROM source_table;
  TYPE  nt_type IS TABLE OF source_table%rowtype;
  l_arr nt_type;
BEGIN
  OPEN c;
  LOOP
    FETCH c BULK COLLECT INTO l_arr LIMIT 100;
    EXIT WHEN l_arr.count = 0;

    FORALL i IN 1 .. l_arr.count
      INSERT INTO dest_table( col1, col2, ... , colN )
        VALUES( l_arr(i).col1, l_arr(i).col2, ... , l_arr(i).colN );
  END LOOP;
END;

Jetzt rufe ich jedes Mal, wenn ich den Abruf ausführe, 100 Datenzeilen in meine Sammlung mit einem einzigen Satz von Kontextverschiebungen ab. Und jedes Mal, wenn ich mein FORALL mache insert, ich füge 100 Zeilen mit einem einzigen Satz von Kontextverschiebungen ein. Wenn source_table 1 Million Zeilen hat, bedeutet dies, dass ich von 4 Millionen Kontextwechseln auf 40.000 Kontextwechsel gegangen bin. Wenn Kontextverschiebungen beispielsweise 20 % der verstrichenen Zeit meines Codes ausmachten, habe ich 19,8 % der verstrichenen Zeit eliminiert.

Sie können die Größe des LIMIT erhöhen Um die Anzahl der Kontextwechsel weiter zu reduzieren, stoßen Sie jedoch schnell auf das Gesetz des abnehmenden Ertrags. Wenn Sie ein LIMIT verwendet haben von 1000 statt 100, würden Sie 99,9 % der Kontextverschiebungen statt 99 % eliminieren. Das würde jedoch bedeuten, dass Ihre Sammlung 10x mehr PGA-Speicher benötigt. Und es würde in unserem hypothetischen Beispiel nur 0,18 % mehr verstrichene Zeit eliminieren. Sie erreichen sehr schnell einen Punkt, an dem der zusätzliche Speicher, den Sie verwenden, mehr Zeit hinzufügt, als Sie sparen, indem Sie zusätzliche Kontextverschiebungen eliminieren. Im Allgemeinen ein LIMIT irgendwo zwischen 100 und 1000 ist wahrscheinlich der ideale Punkt.

In diesem Beispiel wäre es natürlich noch effizienter, alle Kontextwechsel zu eliminieren und alles in einer einzigen SQL-Anweisung zu erledigen

INSERT INTO dest_table( col1, col2, ... , colN )
  SELECT col1, col2, ... , colN
    FROM source_table;

Es wäre nur dann sinnvoll, von vornherein auf PL/SQL zurückzugreifen, wenn Sie eine Art Manipulation der Daten aus der Quelltabelle vornehmen, die Sie in SQL nicht sinnvoll implementieren können.

Außerdem habe ich in meinem Beispiel absichtlich einen expliziten Cursor verwendet. Wenn Sie implizite Cursor verwenden, profitieren Sie in neueren Versionen von Oracle von den Vorteilen eines BULK COLLECT mit einem LIMIT von 100 implizit. Es gibt eine weitere StackOverflow-Frage, die die relativen Leistungsvorteile von impliziten und expliziten Cursorn mit Massenoperationen erörtert, die detaillierter auf diese speziellen Falten eingeht.