Oracle
 sql >> Baza danych >  >> RDS >> Oracle

Oracle:wydajność zbierania zbiorczego

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.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Jak wstępnie określić wyniki zapytania w formacie JSON w SQLcl (Oracle)

  2. Karta sieciowa nie mogła nawiązać połączenia — Oracle 11g

  3. czy jest jakiś sposób na rejestrowanie wszystkich nieudanych instrukcji sql w Oracle 10g?

  4. Powiązanie parametrów zapytania według nazwy z ODP.NET

  5. customer.pk_name dołączanie do transakcji.fk_name vs. customer.pk_id [serial] dołączanie do transakcji.fk_id [liczba całkowita]