Переполнение буфера процедуры - PullRequest
3 голосов
/ 01 ноября 2011

У меня есть следующая процедура, которая заполняет пустые значения в столбце.Процедура работает нормально, если у меня очень маленький набор данных.Но данные, на которые я нацеливаюсь, составляют около 3 миллиардов записей.Простое тестирование этого скрипта на записи 1 млн. Привело к этим исключениям.

ORA-20000: ORU-10027: buffer overflow, limit of 20000 bytes
ORA-06512: at "SYS.DBMS_OUTPUT", line 32
ORA-06512: at "SYS.DBMS_OUTPUT", line 97
ORA-06512: at "SYS.DBMS_OUTPUT", line 112
ORA-06512: at "DBNAME.PRBACKFILLI", line 39
ORA-06512: at line 2

После небольшого копания я понял, что DBMS_OUTPUT.PUT_LINE выводит выходные данные в конце процедуры.Теперь нам нужна информация об отладке, что нам делать?

CREATE OR REPLACE PROCEDURE PRBACKFILL (str_dest IN VARCHAR2)  AS 
  CURSOR cr_pst_ IS
    select id, seq from TABLE_ where ID is null;

  TYPE t_id_array IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
  TYPE t_seq_array IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;

  a_id   t_id_array;
  a_seq  t_seq_array;
  i_bulk_limit  NUMBER := 1000;
BEGIN
  OPEN cr_pst_;
  LOOP
    FETCH cr_pst_
    BULK COLLECT INTO a_id, a_seq LIMIT i_bulk_limit;


    FOR i IN 1..a_id.count LOOP
      a_id(i) := Floor(a_seq(i)/10000000000000);
    END LOOP;

    FORALL i IN 1 .. a_id.count
      UPDATE TABLE_
      SET ID = a_id(i)
      WHERE SEQ = a_seq(i);

      COMMIT;
      DBMS_OUTPUT.PUT_LINE ('COMMITED '||i_bulk_limit||' records');
    EXIT WHEN cr_pst_%NOTFOUND;
  END LOOP; -- main cursor loop
  CLOSE cr_pst_;

  DBMS_OUTPUT.PUT_LINE ('Backfill completed gracefully!');

  EXCEPTION
    WHEN NO_DATA_FOUND THEN
      DBMS_OUTPUT.PUT_LINE('No more records to process');
    WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE('errno: '||TO_CHAR(SQLCODE)||' Msg: ' || SQLERRM);              
END PRBACKFILL;
.
/
sho err;

1 Ответ

11 голосов
/ 02 ноября 2011

Во-первых, вы обычно не используете DBMS_OUTPUT для регистрации. Как правило, было бы гораздо разумнее записать данные в таблицу журнала, особенно если ваша процедура регистрации была определена как автономная транзакция, чтобы вы могли отслеживать данные журнала во время выполнения процедуры. DBMS_OUTPUT будет отображаться только после того, как вся процедура будет завершена, и в этот момент она обычно несколько бессмысленна

Относительно этого первого пункта полагаться на DBMS_OUTPUT, чтобы указать вызывающему абоненту, что произошло какое-то исключение, - очень плохая практика. Как минимум, вам нужно повторно вызвать исключение, которое было сгенерировано, чтобы получить стек ошибок для устранения проблемы.

Во-вторых, когда вы включаете вывод, вы должны указать размер буфера, в который DBMS_OUTPUT может записывать. Похоже, что вы объявили буфер 20 000 байтов, что по умолчанию, если вы просто

SQL> set serveroutput on;

Вы можете изменить это, указав размер, но максимальный размер ограничен 1 000 000 байтов

SQL> set serveroutput on size 1000000;

Если вы планируете обновить 3 миллиарда строк в 1000 кусочках строк, это будет слишком маленький буфер. Вы будете генерировать более чем в 60 раз больше данных с вашим текущим кодом. Если вы используете 10.2 как на клиенте, так и на сервере, вы сможете выделить неограниченный буфер

SQL> set serveroutput on size unlimited;

но это не вариант в более ранних выпусках.

Наконец, вы уверены, что вам нужно в первую очередь прибегнуть к PL / SQL? Похоже, что вы могли бы сделать это более эффективно, просто выполнив одно UPDATE

UPDATE table_
   SET id = floor( seq/ 10000000000000 )
 WHERE id is null;

Это намного меньше кода, его гораздо легче читать, и он будет более эффективным, чем альтернатива PL / SQL. Единственным недостатком является то, что требуется, чтобы ваше табличное пространство UNDO было достаточно большим, чтобы вместить генерируемую UNDO, но при обновлении одного столбца из NULL в ненулевое числовое значение не должно генерироваться столько UNDO.

...