Oracle
 sql >> база данни >  >> RDS >> Oracle

Oracle:производителност на групово събиране

В рамките на 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, който обсъжда относителните ползи от производителността на имплицитните и явните курсори с групови операции, който разглежда по-подробно за тези конкретни бръчки.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. как да замените няколко низа заедно в Oracle

  2. Отпадане на свързан потребител от схема на база данни на Oracle 10g

  3. нежелано водещо празно място в числовия формат на Oracle

  4. Импортирайте CSV файл в таблицата на Oracle с помощта на съхранена процедура

  5. Зареждане на данни от текстов файл в таблица в oracle