W ramach Oracle istnieje maszyna wirtualna SQL (VM) i maszyna wirtualna PL/SQL. Gdy musisz przenieść się z jednej maszyny wirtualnej na drugą, ponosisz koszt zmiany kontekstu. Indywidualnie te zmiany kontekstu są stosunkowo szybkie, ale gdy wykonujesz przetwarzanie wiersz po wierszu, mogą się sumować, aby uwzględnić znaczną część czasu, jaki zajmuje Twój kod. Korzystając z powiązań zbiorczych, przenosisz wiele wierszy danych z jednej maszyny wirtualnej do drugiej za pomocą jednego przesunięcia kontekstu, znacznie zmniejszając liczbę przesunięć kontekstu i przyspieszając kod.
Weźmy na przykład wyraźny kursor. Jeśli napiszę coś takiego
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;
wtedy za każdym razem, gdy wykonuję pobieranie, jestem
- Przeprowadzanie zmiany kontekstu z maszyny wirtualnej PL/SQL do maszyny wirtualnej SQL
- Poproś maszynę wirtualną SQL o wykonanie kursora w celu wygenerowania następnego wiersza danych
- Wykonywanie kolejnego przesunięcia kontekstu z maszyny wirtualnej SQL z powrotem do maszyny wirtualnej PL/SQL w celu zwrócenia jednego wiersza danych
I za każdym razem, gdy wstawiam wiersz, robię to samo. Ponoszę koszt zmiany kontekstu, aby wysłać jeden wiersz danych z maszyny wirtualnej PL/SQL do maszyny wirtualnej SQL, prosząc SQL o wykonanie INSERT
oświadczenie, a następnie poniesienie kosztu innego kontekstu cofnie się do PL/SQL.
Jeśli source_table
ma 1 milion wierszy, to 4 miliony przesunięć kontekstu, które prawdopodobnie będą stanowić rozsądny ułamek czasu, jaki upłynął od mojego kodu. Z drugiej strony, jeśli wykonam BULK COLLECT
z LIMIT
100, mogę wyeliminować 99% moich przesunięć kontekstu, pobierając 100 wierszy danych z maszyny wirtualnej SQL do kolekcji w PL/SQL za każdym razem, gdy ponoszę koszt przesunięcia kontekstu i wstawiając 100 wierszy do tabeli docelowej za każdym razem ponieść tam zmianę kontekstu.
Jeśli mogę przepisać mój kod, aby korzystać z operacji zbiorczych
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;
Teraz za każdym razem, gdy wykonuję pobieranie, pobieram 100 wierszy danych do mojej kolekcji za pomocą jednego zestawu zmian kontekstu. I za każdym razem, gdy robię FORALL
insert, wstawiam 100 wierszy z pojedynczym zestawem przesunięć kontekstu. Jeśli source_table
ma 1 milion wierszy, oznacza to, że przeszedłem od 4 milionów przesunięć kontekstu do 40 000 przesunięć kontekstu. Jeśli zmiany kontekstu stanowiły, powiedzmy, 20% czasu mojego kodu, wyeliminowałem 19,8% tego czasu.
Możesz zwiększyć rozmiar LIMIT
aby jeszcze bardziej zmniejszyć liczbę przesunięć kontekstu, ale szybko trafisz na prawo malejących zwrotów. Jeśli użyłeś LIMIT
1000 zamiast 100, eliminujesz 99,9% zmian kontekstu zamiast 99%. Oznaczałoby to jednak, że twoja kolekcja używała 10x więcej pamięci PGA. W naszym hipotetycznym przykładzie wyeliminowałoby to tylko 0,18% więcej czasu. Bardzo szybko dochodzisz do punktu, w którym dodatkowa pamięć, której używasz, dodaje więcej czasu niż oszczędzasz, eliminując dodatkowe przesunięcia kontekstu. Ogólnie rzecz biorąc, LIMIT
gdzieś pomiędzy 100 a 1000 prawdopodobnie będzie najlepszym miejscem.
Oczywiście w tym przykładzie bardziej efektywne byłoby wyeliminowanie wszystkich przesunięć kontekstu i zrobienie wszystkiego za pomocą jednej instrukcji SQL
INSERT INTO dest_table( col1, col2, ... , colN )
SELECT col1, col2, ... , colN
FROM source_table;
Uciekanie się do PL/SQL miałoby sens tylko wtedy, gdy wykonujesz jakąś manipulację danymi z tabeli źródłowej, której nie możesz rozsądnie zaimplementować w SQL.
Dodatkowo celowo użyłem wyraźnego kursora w moim przykładzie. Jeśli używasz niejawnych kursorów, w najnowszych wersjach Oracle możesz czerpać korzyści z BULK COLLECT
z LIMIT
100 niejawnie. Jest jeszcze jedno pytanie StackOverflow, które omawia względne korzyści wydajności wynikające z niejawnych i jawnych kursorów z operacjami zbiorczymi, które bardziej szczegółowo omawia te konkretne zmarszczki.