3

我有以下程序在列中填充空值。如果我的数据集非常少,该过程可以正常工作。但我的目标数据是大约 30 亿条记录。仅在 100 万条记录上测试此脚本就会引发这些异常。

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;
4

1 回答 1

12

首先,您通常不会DBMS_OUTPUT用于日志记录。将数据写入日志表通常更有意义,特别是如果您的日志记录过程被定义为自治事务,以便您可以在过程运行时监控日志数据。 DBMS_OUTPUT只会在整个过程完成执行后才会显示,此时它通常有点毫无意义。

与第一点相关,依赖于DBMS_OUTPUT向调用者表明存在某种异常是一种非常糟糕的做法。至少,您需要重新引发引发的异常,以便获取错误堆栈以调试问题。

其次,当您启用输出时,您必须指定DBMS_OUTPUT可以写入的缓冲区的大小。您似乎已将缓冲区声明为 20,000 字节,如果您只是简单地

SQL> set serveroutput on;

您可以通过指定大小来更改它,但最大大小限制为 1,000,000 字节

SQL> set serveroutput on size 1000000;

如果您计划更新 1000 行块中的 30 亿行,那么缓冲区将太小。您将使用当前代码生成超过 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 更新为非 NULL 数值不应生成那么多 UNDO。

于 2011-11-01T20:13:29.210 回答