В рамките на Oracle има SQL виртуална машина (VM) и PL/SQL VM. Когато трябва да преминете от една виртуална машина към друга виртуална машина, поемате разходите за промяна на контекста. Поотделно тези промени в контекста са сравнително бързи, но когато извършвате обработка ред по ред, те могат да добавят, за да отчетат значителна част от времето, което прекарва кодът ви. Когато използвате групови обвързвания, премествате множество реда данни от една виртуална машина в друга с едно изместване на контекста, което значително намалява броя на изместванията на контекста, което прави кода ви по-бърз.
Вземете, например, изричен курсор. Ако напиша нещо подобно
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;
тогава всеки път, когато изпълнявам извличането, аз съм
- Извършване на смяна на контекста от PL/SQL VM към SQL VM
- Искане на SQL VM да изпълни курсора, за да генерира следващия ред данни
- Извършване на друго изместване на контекста от SQL VM обратно към PL/SQL VM, за да върна единичния ми ред данни
И всеки път, когато вмъквам ред, правя едно и също нещо. Поемам разходите за промяна на контекста за изпращане на един ред данни от PL/SQL VM към SQL VM, като моля SQL да изпълни INSERT
изявление и след това поема разходите за друго преместване на контекста обратно към PL/SQL.
Ако source_table
има 1 милион реда, това са 4 милиона промени в контекста, които вероятно ще представляват разумна част от изминалото време на моя код. Ако, от друга страна, правя BULK COLLECT
с LIMIT
от 100, мога да премахна 99% от промените в контекста си, като извличам 100 реда данни от SQL VM в колекция в PL/SQL всеки път, когато поема разходите за промяна на контекста и вмъквам 100 реда в таблицата на местоназначението всеки път, когато има промяна в контекста.
Ако мога да пренапиша моя код, за да използвам групови операции
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;
Сега, всеки път, когато изпълнявам извличането, извличам 100 реда данни в моята колекция с един набор от промени в контекста. И всеки път, когато правя моя FORALL
вмъквам, вмъквам 100 реда с един набор от измествания на контекста. Ако source_table
има 1 милион реда, това означава, че преминах от 4 милиона промени в контекста до 40 000 промени в контекста. Ако промените в контекста представляват, да речем, 20% от изминалото време на моя код, елиминих 19,8% от изминалото време.
Можете да увеличите размера на LIMIT
за да намалите допълнително броя на промените в контекста, но бързо постигате закона за намаляващата възвръщаемост. Ако сте използвали LIMIT
от 1000, а не 100, ще елиминирате 99,9% от промените в контекста, а не 99%. Това обаче би означавало, че вашата колекция е използвала 10 пъти повече PGA памет. И това би елиминирало само 0,18% повече изминало време в нашия хипотетичен пример. Много бързо достигате точка, в която допълнителната памет, която използвате, добавя повече време, отколкото спестявате, като елиминирате допълнителните измествания на контекста. Като цяло, LIMIT
някъде между 100 и 1000 вероятно ще бъде сладкото място.
Разбира се, в този пример би било по-ефективно все пак да се премахнат всички промени в контекста и да се направи всичко в един SQL оператор
INSERT INTO dest_table( col1, col2, ... , colN )
SELECT col1, col2, ... , colN
FROM source_table;
Би имало смисъл да прибягвате до PL/SQL на първо място само ако извършвате някаква манипулация на данните от таблицата източник, която не можете разумно да приложите в SQL.
Освен това използвах умишлено изричен курсор в моя пример. Ако използвате неявни курсори, в последните версии на Oracle получавате предимствата на BULK COLLECT
с LIMIT
от 100 имплицитно. Има още един въпрос на StackOverflow, който обсъжда относителните ползи от производителността на имплицитните и явните курсори с групови операции, който разглежда по-подробно за тези конкретни бръчки.