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

Не могли бы вы помочь мне понять эту фразу?

Без массовой привязки PL/SQL отправляет инструкцию SQL механизму SQL для каждой записи, которая вставляется, обновляется или удаляется, что приводит к переключению контекста, снижающему производительность.


person Revious    schedule 12.07.2012    source источник


Ответы (2)


В Oracle есть виртуальная машина SQL (ВМ) и виртуальная машина PL/SQL. Когда вам нужно перейти с одной виртуальной машины на другую, вы понесете затраты на смену контекста. По отдельности эти изменения контекста происходят относительно быстро, но когда вы выполняете построчную обработку, они могут в сумме составлять значительную долю времени, затрачиваемого вашим кодом. При использовании групповых привязок вы перемещаете несколько строк данных с одной виртуальной машины на другую с помощью одного сдвига контекста, что значительно сокращает количество смен контекста и ускоряет код.

Возьмем, к примеру, явный курсор. Если я напишу что-то вроде этого

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 на виртуальную машину SQL
  • Запрос виртуальной машины SQL на выполнение курсора для создания следующей строки данных
  • Выполнение другого переключения контекста с виртуальной машины SQL обратно на виртуальную машину PL/SQL, чтобы вернуть мою единственную строку данных

И каждый раз, когда я вставляю строку, я делаю одно и то же. Я беру на себя затраты на сдвиг контекста, чтобы отправить одну строку данных с виртуальной машины PL/SQL на виртуальную машину SQL, попросить SQL выполнить оператор INSERT, а затем понести затраты на другой сдвиг контекста обратно на PL/SQL.

Если source_table имеет 1 миллион строк, это 4 миллиона сдвигов контекста, которые, вероятно, составят разумную часть затраченного времени моего кода. Если, с другой стороны, я выполняю BULK COLLECT с LIMIT равным 100, я могу исключить 99% сдвигов контекста, извлекая 100 строк данных из виртуальной машины SQL в коллекцию 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, в котором обсуждается относительный преимущества неявных и явных курсоров с массовыми операциями, где более подробно описаны эти особенности.

person Justin Cave    schedule 12.07.2012