程序缓冲区溢出

时间:2011-11-01 19:36:25

标签: oracle logging plsql dbms-output

我有以下程序填充列中的空值。如果我有非常小的数据集,该过程工作正常。但我所针对的数据大约有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;

1 个答案:

答案 0 :(得分:11)

首先,您通常不会使用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。