【问题标题】:Oracle Bulk Collect with Limit and For All Not Processing All Records CorrectlyOracle Bulk Collect with Limit and For All 未正确处理所有记录
【发布时间】:2016-05-20 19:06:34
【问题描述】:

我需要通过存储过程处理 Oracle 表的近 60k 条记录。处理过程是,对于每一个这样的行,我需要在第二个表中删除和更新一行,并在第三个表中插入一行。

使用光标循环,该过程大约需要 6-8 小时才能完成。如果我切换到带限制的批量收集,执行时间会减少但处理不正确。以下是程序的批量收集版本

create or replace procedure myproc()
is
    cursor c1 is select col1,col2,col3 from tab1 where col4=3;
    type t1 is table of c1%rowtype;
    v_t1 t1;
begin
    open c1;
    loop
        fetch c1 bulk collect into v_t1 limit 1000;
        exit when v_t1.count=0;
        forall i in 1..v_t1.count
            delete from tab2 where tab2.col1=v_t1(i).col1;
        commit;
        forall i in 1..v_t1.count
            update tab2 set tab2.col1=v_t1(i).col1 where tab2.col2=v_t1(i).col2;
        commit;
        forall i in 1..v_t1.count
            insert into tab3 values(v_t1(i).col1,v_t1(i).col2,v_t1(i).col3);
        commit;
    end loop;
    close c2;
end;

对于这些记录中的大约 20k,第一次删除操作被正确处理,但后续更新和插入未处理。对于剩余的 40k 条记录,所有三个操作都已正确处理。

我错过了什么吗?另外,我可以对 Bulk Collect 使用的最大 LIMIT 值是多少?

【问题讨论】:

  • 所以你知道你在主循环上迭代了 60 次,你知道你做了 60k 删除但 40k 更新和插入?你是如何检查/计数的?
  • 表tab3是一个日志表,在执行过程之前被清除;因此,如果在执行该过程后该表中有 40k 条记录,则意味着 40k 插入成功。表(tab1)在执行过程之前有接近 2*60k 的记录,执行之后,记录计数为 60k,这意味着 60k 删除成功。
  • 好的,tab2.col1 是唯一的吗?我不是想变得困难,只是想想想你可能忽略了什么。
  • 是的,这是独一无二的,而且是一个具体的数字。

标签: oracle plsql cursor forall bulk-collect


【解决方案1】:

您应该尝试使用 FORALL 的 SAVE EXCEPTIONS 子句,例如(未经测试):

create or replace procedure myproc
as
    cursor c1 is select col1,col2,col3 from tab1 where col4=3;
    type t1 is table of c1%rowtype;
    v_t1 t1;
    dml_errors EXCEPTION;
    PRAGMA exception_init(dml_errors, -24381);
    l_errors number;
    l_errno    number;
    l_msg    varchar2(4000);
    l_idx    number;
begin
    open c1;
    loop
        fetch c1 bulk collect into v_t1 limit 1000;
        -- process v_t1 data
        BEGIN
            forall i in 1..v_t1.count SAVE EXCEPTIONS
                delete from tab2 where tab2.col1=v_t1(i).col1;
            commit;
        EXCEPTION
            when DML_ERRORS then
                l_errors := sql%bulk_exceptions.count;
                for i in 1 .. l_errors
                loop
                    l_errno := sql%bulk_exceptions(i).error_code;
                    l_msg   := sqlerrm(-l_errno);
                    l_idx   := sql%bulk_exceptions(i).error_index;
                    -- log these to a table maybe, or just output 
                end loop;
        END;

        exit when c1%notfound;
    end loop;
    close c2;
end;

【讨论】:

  • 所以你的意思是说,那些 20k 记录没有被处理,因为我需要捕获和记录/打印以调试问题的一些异常?但是,如果确实存在异常,那么 Toad(我正在执行存储过程的位置)不应该给我消息并突然终止执行吗?
  • 不,我只是说它在尝试调试发生的事情时很有帮助。我们没有具体的数据,所以我们猜测了一下。此外,您可能没有像您想象的那样更新/插入尽可能多的行,不一定是操作没有发生(它可以运行语句并插入或更新 0 行)。要知道这一点,您需要在每个 forall 语句之后立即添加 SQL%ROWCOUNT。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多