sql >> Database teknologi >  >> RDS >> Oracle

Oracle:Bulk Collect-ydelse

Inden for Oracle er der en virtuel SQL-maskine (VM) og en PL/SQL VM. Når du skal flytte fra den ene VM til den anden VM, pådrager du dig omkostningerne ved et kontekstskift. Individuelt er disse kontekstskift relativt hurtige, men når du udfører række-for-række-behandling, kan de tælle sammen og udgøre en betydelig brøkdel af den tid, din kode bruger. Når du bruger massebindinger, flytter du flere rækker af data fra den ene VM til den anden med et enkelt kontekstskift, hvilket reducerer antallet af kontekstskift markant, hvilket gør din kode hurtigere.

Tag for eksempel en eksplicit markør. Hvis jeg skriver sådan noget her

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;

så hver gang jeg udfører hentning, er jeg

  • Udførelse af et kontekstskift fra PL/SQL VM til SQL VM
  • At bede SQL VM'en om at udføre markøren for at generere den næste række data
  • Udførelse af endnu et kontekstskift fra SQL VM tilbage til PL/SQL VM for at returnere min enkelte række af data

Og hver gang jeg indsætter en række, gør jeg det samme. Jeg afholder omkostningerne ved et kontekstskift for at sende en række data fra PL/SQL VM til SQL VM, og beder SQL om at udføre INSERT erklæring, og derefter afholde omkostningerne ved et andet kontekstskift tilbage til PL/SQL.

Hvis source_table har 1 million rækker, det er 4 millioner kontekstskift, som sandsynligvis vil udgøre en rimelig brøkdel af den forløbne tid af min kode. Hvis jeg derimod laver en BULK COLLECT med en LIMIT af 100, kan jeg eliminere 99 % af mine kontekstskift ved at hente 100 rækker data fra SQL VM til en samling i PL/SQL, hver gang jeg pådrager mig omkostningerne ved et kontekstskift og indsætte 100 rækker i destinationstabellen, hver gang jeg pådrage sig et kontekstskifte der.

If kan omskrive min kode for at gøre brug af bulk operationer

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, hver gang jeg udfører hentning, henter jeg 100 rækker data ind i min samling med et enkelt sæt kontekstskift. Og hver gang jeg laver min FORALL indsæt, jeg indsætter 100 rækker med et enkelt sæt kontekstskift. Hvis source_table har 1 million rækker, betyder det, at jeg er gået fra 4 millioner kontekstskift til 40.000 kontekstskift. Hvis kontekstskift udgjorde f.eks. 20 % af den forløbne tid af min kode, har jeg elimineret 19,8 % af den forløbne tid.

Du kan øge størrelsen på LIMIT for yderligere at reducere antallet af kontekstskift, men du rammer hurtigt loven om faldende afkast. Hvis du brugte en LIMIT af 1000 i stedet for 100, ville du eliminere 99,9 % af kontekstskiftene i stedet for 99 %. Det ville dog betyde, at din samling brugte 10 gange mere PGA-hukommelse. Og det ville kun eliminere 0,18 % mere forløbet tid i vores hypotetiske eksempel. Du når meget hurtigt et punkt, hvor den ekstra hukommelse, du bruger, tilføjer mere tid, end du sparer, ved at eliminere yderligere kontekstskift. Generelt en LIMIT et sted mellem 100 og 1000 er sandsynligvis det søde sted.

Selvfølgelig ville det i dette eksempel være mere effektivt at eliminere alle kontekstskift og gøre alt i en enkelt SQL-sætning

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

Det ville kun give mening at ty til PL/SQL i første omgang, hvis du laver en form for manipulation af dataene fra kildetabellen, som du ikke med rimelighed kan implementere i SQL.

Derudover brugte jeg en eksplicit markør i mit eksempel med vilje. Hvis du bruger implicitte markører, får du i nyere versioner af Oracle fordelene ved en BULK COLLECT med en LIMIT på 100 implicit. Der er et andet StackOverflow-spørgsmål, der diskuterer de relative ydeevnefordele ved implicitte og eksplicitte markører med bulk-operationer, der går mere i detaljer om disse særlige rynker.



  1. Ydelse af sys.partitioner

  2. Indfang udførelsesplanadvarsler ved hjælp af udvidede hændelser

  3. MySQL:Sorter GROUP_CONCAT-værdier

  4. Sådan bygger du ubegrænset menuniveau gennem PHP og mysql