Binnen Oracle is er een virtuele SQL-machine (VM) en een PL/SQL-VM. Wanneer u van de ene VM naar de andere VM moet gaan, loopt u de kosten van een contextverschuiving op. Individueel zijn die contextverschuivingen relatief snel, maar wanneer u rij-voor-rij verwerking uitvoert, kunnen ze oplopen tot een aanzienlijk deel van de tijd die uw code besteedt. Wanneer u bulkbindingen gebruikt, verplaatst u meerdere rijen gegevens van de ene VM naar de andere met een enkele contextverschuiving, waardoor het aantal contextverschuivingen aanzienlijk wordt verminderd en uw code sneller wordt.
Neem bijvoorbeeld een expliciete cursor. Als ik zoiets schrijf
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;
dan ben ik elke keer dat ik de ophaalactie uitvoer
- Een contextverschuiving uitvoeren van de PL/SQL-VM naar de SQL-VM
- De SQL VM vragen om de cursor uit te voeren om de volgende rij gegevens te genereren
- Een andere contextverschuiving uitvoeren van de SQL-VM terug naar de PL/SQL-VM om mijn enkele rij met gegevens te retourneren
En elke keer dat ik een rij invoeg, doe ik hetzelfde. Ik maak de kosten van een contextverschuiving om één rij gegevens van de PL/SQL-VM naar de SQL-VM te verzenden, waarbij ik de SQL vraag om de INSERT
uit te voeren. statement, en vervolgens de kosten van een andere contextverschuiving terug naar PL/SQL.
Als source_table
heeft 1 miljoen rijen, dat zijn 4 miljoen contextverschuivingen die waarschijnlijk een redelijke fractie van de verstreken tijd van mijn code zullen uitmaken. Als ik daarentegen een BULK COLLECT
. doe met een LIMIT
van 100, kan ik 99% van mijn contextverschuivingen elimineren door 100 rijen met gegevens van de SQL-VM op te halen in een verzameling in PL/SQL elke keer dat ik de kosten van een contextverschuiving maak en 100 rijen in te voegen in de doeltabel elke keer dat ik daar een contextverschuiving veroorzaken.
If kan mijn code herschrijven om gebruik te maken van bulkbewerkingen
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, elke keer dat ik de fetch uitvoer, haal ik 100 rijen gegevens op in mijn verzameling met een enkele set contextverschuivingen. En elke keer dat ik mijn FORALL
. doe insert, ik voeg 100 rijen in met een enkele set contextverschuivingen. Als source_table
1 miljoen rijen heeft, betekent dit dat ik van 4 miljoen contextverschuivingen naar 40.000 contextverschuivingen ben gegaan. Als contextverschuivingen verantwoordelijk waren voor bijvoorbeeld 20% van de verstreken tijd van mijn code, heb ik 19,8% van de verstreken tijd geëlimineerd.
U kunt de grootte van de LIMIT
. vergroten om het aantal contextverschuivingen verder te verminderen, maar je raakt al snel de wet van de afnemende meeropbrengst. Als je een LIMIT
. hebt gebruikt van 1000 in plaats van 100, zou je 99,9% van de contextverschuivingen elimineren in plaats van 99%. Dat zou echter betekenen dat uw verzameling 10x meer PGA-geheugen gebruikte. En het zou in ons hypothetische voorbeeld slechts 0,18% meer verstreken tijd elimineren. Je bereikt heel snel een punt waarop het extra geheugen dat je gebruikt meer tijd toevoegt dan je bespaart door extra contextverschuivingen te elimineren. Over het algemeen geldt een LIMIT
ergens tussen de 100 en 1000 is waarschijnlijk de goede plek.
In dit voorbeeld zou het natuurlijk nog efficiënter zijn om alle contextverschuivingen te elimineren en alles in een enkele SQL-instructie te doen
INSERT INTO dest_table( col1, col2, ... , colN )
SELECT col1, col2, ... , colN
FROM source_table;
Het zou alleen zinvol zijn om in de eerste plaats gebruik te maken van PL/SQL als u een soort manipulatie van de gegevens uit de brontabel uitvoert die u redelijkerwijs niet in SQL kunt implementeren.
Bovendien heb ik opzettelijk een expliciete cursor in mijn voorbeeld gebruikt. Als u impliciete cursors gebruikt, krijgt u in recente versies van Oracle de voordelen van een BULK COLLECT
met een LIMIT
van 100 impliciet. Er is nog een StackOverflow-vraag die de relatieve prestatievoordelen van impliciete en expliciete cursors met bulkbewerkingen bespreekt die meer in detail gaan over die specifieke rimpels.