V rámci Oracle existuje virtuální stroj SQL (VM) a virtuální počítač PL/SQL. Když se potřebujete přesunout z jednoho virtuálního počítače na druhý, vynaložíte náklady na posun kontextu. Jednotlivě jsou tyto změny kontextu relativně rychlé, ale když provádíte zpracování řádek po řádku, mohou se sčítat a představovat významný zlomek času, který váš kód stráví. Když používáte hromadné vazby, přesouváte více řádků dat z jednoho virtuálního počítače do druhého s jediným posunem kontextu, čímž se výrazně snižuje počet posunů kontextu a váš kód je rychlejší.
Vezměte si například explicitní kurzor. Pokud něco takového napíšu
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;
pak pokaždé, když provedu načtení, jsem
- Provedení přesunu kontextu z virtuálního počítače PL/SQL na virtuální počítač SQL
- Požádání virtuálního počítače SQL, aby spustil kurzor a vygeneroval další řádek dat
- Provedení dalšího posunu kontextu z virtuálního počítače SQL zpět na virtuální počítač PL/SQL, aby se vrátil můj jeden řádek dat
A pokaždé, když vložím řádek, dělám to samé. Vznikají mi náklady na kontextový posun za účelem odeslání jednoho řádku dat z virtuálního počítače PL/SQL do virtuálního počítače SQL a žádám SQL, aby provedl INSERT
a pak vzniknou náklady na další kontextový posun zpět do PL/SQL.
Pokud source_table
má 1 milion řádků, to jsou 4 miliony kontextových posunů, které budou pravděpodobně představovat rozumný zlomek uplynulého času mého kódu. Pokud na druhou stranu udělám BULK COLLECT
s LIMIT
ze 100, mohu eliminovat 99 % mých kontextových posunů načtením 100 řádků dat z SQL VM do kolekce v PL/SQL pokaždé, když mi vzniknou náklady na kontextový posun, a vložením 100 řádků do cílové tabulky pokaždé, když tam dojde ke změně kontextu.
Pokud mohu přepsat můj kód, aby bylo možné využívat hromadné operace
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;
Nyní pokaždé, když provedu načtení, získám 100 řádků dat do své kolekce s jedinou sadou kontextových posunů. A pokaždé, když udělám svůj FORALL
vložit, vkládám 100 řádků s jedinou sadou kontextových posunů. Pokud source_table
má 1 milion řádků, to znamená, že jsem přešel ze 4 milionů kontextových posunů na 40 000 kontextových posunů. Pokud změny kontextu představovaly řekněme 20 % uplynulého času mého kódu, odstranil jsem 19,8 % uplynulého času.
Můžete zvětšit velikost LIMIT
k dalšímu snížení počtu kontextových posunů, ale rychle narazíte na zákon klesajících výnosů. Pokud jste použili LIMIT
z 1000 namísto 100, odstranili byste 99,9 % kontextových posunů spíše než 99 %. To by však znamenalo, že vaše sbírka využívala 10x více paměti PGA. A v našem hypotetickém příkladu by to eliminovalo pouze o 0,18 % více uplynulého času. Velmi rychle dosáhnete bodu, kdy další paměť, kterou používáte, přidává více času, než ušetříte tím, že eliminujete další posuny kontextu. Obecně LIMIT
někde mezi 100 a 1000 bude pravděpodobně nejpříjemnější místo.
V tomto příkladu by samozřejmě bylo ještě efektivnější eliminovat všechny posuny kontextu a udělat vše v jediném příkazu SQL
INSERT INTO dest_table( col1, col2, ... , colN )
SELECT col1, col2, ... , colN
FROM source_table;
Uchýlit se k PL/SQL by dávalo smysl pouze v případě, že provádíte nějakou manipulaci s daty ze zdrojové tabulky, kterou nemůžete rozumně implementovat v SQL.
Navíc jsem ve svém příkladu záměrně použil explicitní kurzor. Pokud používáte implicitní kurzory, v posledních verzích Oracle získáte výhody BULK COLLECT
s LIMIT
100 implicitně. Existuje další otázka StackOverflow, která pojednává o výhodách relativního výkonu implicitních a explicitních kurzorů s hromadnými operacemi, které se podrobněji zabývají těmito konkrétními vráskami.