2012-07-12 13 views
7

Czy możesz pomóc mi zrozumieć to zdanie?Oracle: Bulk Collect performance

Bez wiążą luzem, PL/SQL wysyła zapytanie SQL do silnika SQL dla każdego rekordu, który jest wstawiony, aktualizowania lub usuwania prowadzi do przełączników kontekstowych że boli wydajność.

Odpowiedz

17

W Oracle istnieje maszyna wirtualna SQL (VM) i maszyna wirtualna PL/SQL. Kiedy musisz przejść z jednej maszyny wirtualnej na inną maszynę wirtualną, poniesiesz koszt zmiany kontekstu. Indywidualnie, te zmiany kontekstu są stosunkowo szybkie, ale podczas przetwarzania wiersz po rzędzie mogą one stanowić znaczną część czasu wydawania kodu. Podczas korzystania z powiązań zbiorczych przenosisz wiele wierszy danych z jednej maszyny wirtualnej do drugiej przy użyciu pojedynczej zmiany kontekstu, znacznie zmniejszając liczbę przesunięć kontekstowych, dzięki czemu kod jest szybszy.

Weźmy na przykład wyraźny kursor. Jeśli piszę 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; 

potem za każdym razem jak wykonać fetch, jestem

  • Performing przesunięcie kontekstowe z PL/SQL VM do SQL VM
  • sprzedaży SQL VM aby uruchomić kursor w celu wygenerowania następnego wiersza danych:
  • Wykonanie kolejnego przejścia kontekstowego z maszyny wirtualnej SQL z powrotem na maszynę wirtualną PL/SQL, aby zwrócić pojedynczy wiersz danych

I za każdym razem, gdy wstawiam wiersz, robię to samo. Koszt zmiany kontekstu polega na wysłaniu jednego wiersza danych z maszyny wirtualnej PL/SQL do maszyny wirtualnej SQL, co spowoduje, że SQL wykona instrukcję INSERT, a następnie poniesie koszt kolejnego przeniesienia kontekstu z powrotem do PL/SQL.

Jeśli source_table ma 1 milion wierszy, to 4 miliony zmian kontekstowych, które prawdopodobnie będą stanowić rozsądną część czasu, jaki upłynął od mojego kodu. Jeśli, z drugiej strony, wykonuję BULK COLLECT z LIMIT z 100, mogę wyeliminować 99% moich zmian kontekstu, pobierając 100 wierszy danych z maszyny wirtualnej SQL do kolekcji w PL/SQL za każdym razem, gdy poniosę koszt przesunięcie kontekstu i wstawienie 100 wierszy do tabeli docelowej za każdym razem, gdy wprowadzę tam przesunięcie kontekstu.

Jeśli można przepisać mój kod do wykorzystania operacji masowych

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 wykonać sprowadzić, odzyskać 100 wierszy danych w mojej kolekcji z jednego zestawu zmian kontekstu. I za każdym razem, gdy robię moją wstawkę FORALL, wstawiam 100 wierszy z jednym zestawem przesunięć kontekstowych. Jeśli source_table ma 1 milion wierszy, oznacza to, że przeszedłem od 4 milionów zmian kontekstowych do 40 000 zmian kontekstowych. Jeśli przesunięcie kontekstu wyniosło, powiedzmy, 20% czasu, jaki upłynął od mojego kodu, wyeliminowałem 19.8% czasu, który upłynął.

Możesz zwiększyć rozmiar LIMIT, aby jeszcze bardziej zmniejszyć liczbę zmian kontekstu, ale szybko osiągniesz prawo zmniejszających się zwrotów. Jeśli użyjesz LIMIT z 1000 zamiast 100, wyeliminujesz 99,9% przesunięć kontekstowych zamiast 99%. Oznaczałoby to, że Twoja kolekcja używa 10 razy więcej pamięci PGA. W naszym hipotetycznym przykładzie wyeliminowałoby to tylko o 0.18% więcej czasu. Szybko osiągasz punkt, w którym dodatkowa pamięć, której używasz, zapewnia więcej czasu niż oszczędzasz, eliminując dodatkowe przesunięcia kontekstowe. Ogólnie rzecz biorąc, najprawdopodobniej najlepszym rozwiązaniem będzie LIMIT, gdzieś pomiędzy 100 a 1000.

Oczywiście, w tym przypadku, to byłoby bardziej efektywne jeszcze wyeliminować wszystkie zmiany kontekstu i zrobić wszystko w jednym SQL

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

Byłoby sens tylko do uciekania się do PL/SQL w pierwsze miejsce, jeśli robisz jakieś manipulowanie danymi z tabeli źródłowej, których nie możesz racjonalnie zaimplementować w SQL.

Dodatkowo celowo użyłem jawnego kursora w moim przykładzie. Jeśli używasz niejawnych kursorów, w najnowszych wersjach Oracle uzyskasz korzyści z BULK COLLECT ze 1002 wartościami domyślnymi z LIMIT. Jest jeszcze jedno pytanie StackOverflow, które omawia względną performance benefits of implicit and explicit cursors with bulk operations, która idzie bardziej szczegółowo na temat tych konkretnych zmarszczek.

1

Jak rozumiem, w grę wchodzą dwa silniki, PL/SQL engine and SQL Engine. Wykonanie zapytania, które korzystają z jednego silnika w czasie, jest bardziej wydajny niż przejście pomiędzy dwoma

Przykład:

INSERT INTO t VALUES(1) 

jest przetwarzana przez silnik SQL podczas

FOR Lcntr IN 1..20 

    END LOOP 

jest wykonywany przez PL/SQL engine

Jeśli połączysz powyższe dwie instrukcje, wstawiając INSERT w pętlę,

FOR Lcntr IN 1..20 
    INSERT INTO t VALUES(1) 
END LOOP 

Oracle będzie przełączać się między dwoma silnikami, dla każdej (20) iteracji. W tym przypadku zalecany jest BULK INSERT, który korzysta z silnika PL/SQL przez cały czas wykonywania

+0

Twoje ostatnie zdanie jest trochę zwodnicze. BULK sprawia, że ​​zmiana kontekstu ma miejsce tylko raz, chociaż wciąż się zdarza. – viper