Oracle Bulk Collect with Limit and For All 未正确处理所有记录

Posted

技术标签:

【中文标题】Oracle Bulk Collect with Limit and For All 未正确处理所有记录【英文标题】:Oracle Bulk Collect with Limit and For All Not Processing All Records Correctly 【发布时间】: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 是唯一的吗?我不是想变得困难,只是想想想你可能忽略了什么。 是的,这是独一无二的,而且是一个具体的数字。 【参考方案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。

以上是关于Oracle Bulk Collect with Limit and For All 未正确处理所有记录的主要内容,如果未能解决你的问题,请参考以下文章

Oracle Bulk Collect and Update from cursor or user Update with Select 子查询

Oracle bulk collect into 的几种用法

重构 Oracle 存储过程以使用 BULK COLLECT

带有 BULK COLLECT 的 Oracle PL/SQL 6504

ORACLE批量绑定FORALL与BULK COLLECT

oracle plsql中同时记录类型、Collection和Bulk collect