sql >> Databasteknik >  >> RDS >> Oracle

Oracle:Bulk Collect-prestanda

Inom Oracle finns en virtuell SQL-maskin (VM) och en PL/SQL VM. När du behöver flytta från en virtuell dator till en annan virtuell dator får du kostnaden för ett kontextskifte. Individuellt är dessa kontextskiften relativt snabba, men när du bearbetar rad för rad kan de läggas ihop för att stå för en betydande del av tiden som din kod spenderar. När du använder bulkbindningar flyttar du flera rader med data från en virtuell dator till den andra med en enda kontextförskjutning, vilket avsevärt minskar antalet kontextförskjutningar, vilket gör din kod snabbare.

Ta till exempel en explicit markör. Om jag skriver något sånt här

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;

sedan gör jag det varje gång jag utför hämtningen

  • Utföra ett kontextskifte från PL/SQL VM till SQL VM
  • Be SQL VM att köra markören för att generera nästa rad med data
  • Utföra ytterligare ett kontextskifte från SQL VM tillbaka till PL/SQL VM för att returnera min enda rad med data

Och varje gång jag sätter in en rad gör jag samma sak. Jag ådrar mig kostnaden för ett kontextskifte för att skicka en rad med data från PL/SQL VM till SQL VM, och ber SQL att exekvera INSERT uttalande och sedan ådra sig kostnaden för ett annat kontextskifte tillbaka till PL/SQL.

Om source_table har 1 miljon rader, det är 4 miljoner kontextförskjutningar som sannolikt kommer att stå för en rimlig bråkdel av den förflutna tiden av min kod. Om jag å andra sidan gör en BULK COLLECT med en LIMIT av 100, kan jag eliminera 99 % av mina kontextförskjutningar genom att hämta 100 rader med data från SQL VM till en samling i PL/SQL varje gång jag tar på mig kostnaden för en kontextförskjutning och infoga 100 rader i måltabellen varje gång jag medföra en kontextförskjutning där.

If kan skriva om min kod för att använda bulkoperationer

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;

Nu, varje gång jag kör hämtningen, hämtar jag 100 rader med data till min samling med en enda uppsättning kontextskiftningar. Och varje gång jag gör min FORALL infoga, jag infogar 100 rader med en enda uppsättning kontextskiftningar. Om source_table har 1 miljon rader, betyder det att jag har gått från 4 miljoner kontextskiften till 40 000 kontextskiften. Om kontextförskjutningar stod för t.ex. 20 % av den förflutna tiden av min kod, har jag eliminerat 19,8 % av den förflutna tiden.

Du kan öka storleken på LIMIT för att ytterligare minska antalet kontextskiften men du slår snabbt mot lagen om minskande avkastning. Om du använde en LIMIT av 1000 istället för 100 skulle du eliminera 99,9 % av kontextförskjutningarna istället för 99 %. Det skulle dock betyda att din samling använde 10 gånger mer PGA-minne. Och det skulle bara eliminera 0,18 % mer förfluten tid i vårt hypotetiska exempel. Du når mycket snabbt en punkt där det extra minnet du använder lägger till mer tid än du sparar genom att eliminera ytterligare kontextförskjutningar. I allmänhet en LIMIT någonstans mellan 100 och 1000 är sannolikt det bästa stället.

Naturligtvis, i det här exemplet skulle det fortfarande vara mer effektivt att eliminera alla kontextskiften och göra allt i en enda SQL-sats

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

Det skulle bara vara vettigt att tillgripa PL/SQL i första hand om du gör någon form av manipulation av data från källtabellen som du inte rimligen kan implementera i SQL.

Dessutom använde jag en explicit markör i mitt exempel avsiktligt. Om du använder implicita markörer, i de senaste versionerna av Oracle, får du fördelarna med en BULK COLLECT med en LIMIT av 100 implicit. Det finns en annan StackOverflow-fråga som diskuterar de relativa prestandafördelarna med implicita och explicita markörer med bulkoperationer som går in mer i detalj om just dessa rynkor.



  1. Vad är SQL?

  2. SQL:Vad är standardordningen efter för frågor?

  3. Fel:Ingen modul med namnet psycopg2.extensions

  4. Uppdatera SQL-läge i MySQL