【问题标题】:ORA-01555 error when updating 200 million rows with BULK COLLECT使用 BULK COLLECT 更新 2 亿行时出现 ORA-01555 错误
【发布时间】:2017-03-15 16:24:48
【问题描述】:

我有以下 PL/SQL 代码,它会更新大约 2 亿行的表中每一行的一列。我使用 BULK COLLECT 从表中重复获取 150,000 行并更新这些行。我在 50,000 次更新后提交。

DECLARE
    CURSOR jobs_cursor IS 
        SELECT e.ID, e.PTI, e.CAT, e.JOBNAME, e.JOBDATE, e.WORK_DESCRIPTION
                FROM JOB e 
                WHERE length(e.WORK_DESCRIPTION) > 1000;

    TYPE JOBS_TYPE IS TABLE OF jobs_cursor%ROWTYPE;
    v_jobs JOBS_TYPE;
    fetch_jobs_limit PLS_INTEGER := 150000;

    trimmed_work_description VARCHAR2(2000 CHAR);
    sub_string_work_description_left VARCHAR2(1000 CHAR);
    sub_string_work_description_right VARCHAR2(1000 CHAR);
    update_counter NUMBER := 0;
    commit_counter NUMBER := 50000;

BEGIN
    OPEN jobs_cursor;
    LOOP
        FETCH jobs_cursor BULK COLLECT INTO v_jobs LIMIT fetch_jobs_limit;
        EXIT WHEN v_jobs.COUNT = 0;

        FOR idx IN 1..v_jobs.COUNT
        LOOP
            trimmed_work_description := ' ';

            IF v_jobs(idx).WORK_DESCRIPTION IS NOT NULL THEN
                trimmed_work_description := TRIM(TRAILING ' ' FROM v_jobs(idx).WORK_DESCRIPTION);
            END IF;

            IF length(trimmed_work_description) <= 1000 THEN
                UPDATE JOBS j SET j.WORK_DESCRIPTION = trimmed_work_description WHERE j.ID = v_jobs(idx).ID;

                update_counter := update_counter + 1;
                IF mod(update_counter, commit_counter) = 0 THEN
                    COMMIT;
                    update_counter := 0;
                END IF;
                CONTINUE;

            ELSIF length(trimmed_work_description) > 1000 THEN
                sub_string_work_description_left := SUBSTR(trimmed_work_description, 1, 1000);
                sub_string_work_description_right := SUBSTR(trimmed_work_description, 1001, 2000);
            END IF;

            UPDATE JOBS j SET j.WORK_DESCRIPTION = sub_string_work_description_left WHERE j.ID = v_jobs(idx).ID;
            INSERT INTO JOBS j VALUES ("SEQUENCE_JOBS".NEXTVAL, j.PTI, j.CAT, j.JOBNAME, j.JOBDATE, sub_string_work_description_right);

            update_counter := update_counter + 1;
            IF mod(update_counter, commit_counter) = 0 THEN
                COMMIT;
                update_counter := 0;
            END IF;

        END LOOP;
    END LOOP;
    COMMIT;
    CLOSE jobs_cursor;
END;

代码运行了几个小时,但随后 Oracle 引发了 ORA-01555 - Snapshot too old - Rollback segment number 14 with name xxxx too small

您能告诉我我的 PL/SQL 出了什么问题吗?我已经做了谷歌研究,发现一些线程说可以通过扩展 UNDO 表空间来避免这个错误,但是在我的情况下这不是一个选项。因此,我需要修改 PL/SQL 代码。

【问题讨论】:

  • 你应该在这一行得到一个错误:IF v_jobs(idx).MESSAGE IS NOT NULL THENMESSAGE 不存在于记录中。
  • 我不是向您展示了一种在另一个线程中使用重定义与转换来实现此目的的技术吗...?
  • @WernfriedDomscheit 打错字了,抱歉。消息不正确。我更正了代码。
  • @BobC 是的,你做到了,但我只是想知道 Oracle 引发错误的原因。我是 Oracle 的新手,我想从我的错误中尽可能多地学习。如果有人能向我解释为什么我的代码会引发错误,我将不胜感激,这样我以后就可以避免这样的问题。

标签: oracle plsql oracle11g bulk-load


【解决方案1】:

在第一个视图中,我看不出您为什么要在循环中进行更新,单条语句应该是可能的。将与此类似(未经验证/测试)

update JOBS j SET 
WORK_DESCRIPTION = SUBSTR(TRIM(TRAILING ' ' FROM WORK_DESCRIPTION), 1, 1000)
WHERE length(WORK_DESCRIPTION) > 1000;

INSERT INTO JOBS 
SELECT SEQUENCE_JOBS.NEXTVAL, j.PTI, j.CAT, j.JOBNAME, j.JOBDATE, 
   SUBSTR(WORK_DESCRIPTION, 1001, 2000)
FROM JOBS j
WHERE length(TRIM(TRAILING ' ' FROM WORK_DESCRIPTION)) > 1000;

【讨论】:

  • 如果我在一个 UPDATE 语句中进行更新,我会不会遇到撤消表空间的问题?
  • 如果 UNDO 表空间足够大,则不会,但是您已经遇到麻烦了,因为导致 ORA-01555 错误的是中间提交。那么为什么不尝试一种不同的方法来避免您现在实际遇到的问题
  • 我必须对 2 亿行中的每一行进行更新。我已经对选定的行执行了 COUNT(*)。
  • @APC 你有什么建议?
  • 试试 Wernfried 的方法。或@BoBC 建议的任何内容。如果您不接受他们的建议,为什么还要麻烦人们在 SO 上为您提供帮助?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-05-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-10
  • 1970-01-01
相关资源
最近更新 更多