使用“更新”批量收集

Posted

技术标签:

【中文标题】使用“更新”批量收集【英文标题】:bulk collect using "for update" 【发布时间】:2014-02-07 20:49:07 【问题描述】:

我在使用 BULK COLLECT 在 Oracle (11g) 中处理记录时遇到了一个有趣且意外的问题。

以下代码运行良好,处理了所有百万多条记录而没有出现问题:

-- Define cursor
cursor My_Data_Cur Is
Select col1
      ,col2
from My_Table_1;
…

-- Open the cursor
open My_Data_Cur;

-- Loop through all the records in the cursor
loop

  -- Read the first group of records
  fetch My_Data_Cur
  bulk collect into My_Data_Rec
  limit 100;

  -- Exit when there are no more records to process
  Exit when My_Data_Rec.count = 0;

  -- Loop through the records in the group
  for idx in 1 .. My_Data_Rec.count
  loop
    … do work here to populate a records to be inserted into My_Table_2 …
  end loop;

  -- Insert the records into the second table
  forall idx in 1 .. My_Data_Rec.count
  insert into My_Table_2…;

  -- Delete the records just processed from the source table
  forall idx in 1 .. My_Data_Rec.count
  delete from My_Table_1 …;

  commit;
end loop;

由于在处理每组 100 条记录(限制为 100 条)结束时,我们正在删除刚刚读取和处理的记录,但我认为将“for update”语法添加到游标定义中是个好主意,以便在读取数据和删除记录之间,另一个进程无法更新任何记录。

所以,我更改的代码中唯一的事情是……

cursor My_Data_Cur
is
  select col1
        ,col2
from My_Table_1
for update;

当我在此更改后运行 PL/SQL 包时,作业仅处理 100 条记录,然后终止。我通过从游标中删除“for update”确认此更改导致了问题,并且包再次处理了源表中的所有记录。

任何想法为什么添加“for update”子句会导致这种行为变化?有关如何解决此问题的任何建议?我将尝试在流程开始时在表上启动独占事务,但这不是一个理想的解决方案,因为我真的不想锁定处理数据的整个表。

提前感谢您的帮助,

授予

【问题讨论】:

【参考方案1】:

问题是您正在尝试跨提交进行提取。

当您使用for update 子句打开My_Data_Cur 时,Oracle 必须先锁定My_Data_1 表中的每一行,然后才能返回任何行。当您commit 时,Oracle 必须释放所有这些锁(Oracle 创建的锁不跨越事务)。由于游标不再具有您请求的锁,Oracle 必须关闭游标,因为它不再满足 for update 子句。因此,第二次提取必须返回 0 行。

最合乎逻辑的方法几乎总是删除commit 并在单个事务中完成整个操作。如果您真的,真的,真的需要单独的事务,则需要为循环的每次迭代打开和关闭游标。最有可能的是,您想要做一些事情来限制游标每次打开时只返回 100 行(即 rownum <= 100 子句),这样您就不会产生访问每一行来放置锁和然后除您处理和删除的 100 行之外的每一行,以在每次循环中释放锁。

【讨论】:

贾斯汀 - 感谢您的快速回复,感谢您列出了几个选项!【参考方案2】:

添加到贾斯汀的解释。

您应该已经看到以下错误消息。不确定,如果您的 Exception 处理程序抑制了这一点。

消息本身说明了很多!

对于这种Updates,最好创建一个主表的影子副本,并让公共同义词指向它。虽然一些批处理 id 会为我们的主表创建一个私有同义词并执行批处理操作,以使其更易于维护。

Error report -
ORA-01002: fetch out of sequence
ORA-06512: at line 7
01002. 00000 -  "fetch out of sequence"
*Cause:    This error means that a fetch has been attempted from a cursor
           which is no longer valid.  Note that a PL/SQL cursor loop
           implicitly does fetches, and thus may also cause this error.
           There are a number of possible causes for this error, including:
           1) Fetching from a cursor after the last row has been retrieved
           and the ORA-1403 error returned.
           2) If the cursor has been opened with the FOR UPDATE clause,
           fetching after a COMMIT has been issued will return the error.
           3) Rebinding any placeholders in the SQL statement, then issuing
           a fetch before reexecuting the statement.
*Action:   1) Do not issue a fetch statement after the last row has been
           retrieved - there are no more rows to fetch.
           2) Do not issue a COMMIT inside a fetch loop for a cursor
           that has been opened FOR UPDATE.
           3) Reexecute the statement after rebinding, then attempt to
           fetch again.

另外,你可以使用rowid改变你的逻辑

Docs 的示例:

DECLARE
-- if "FOR UPDATE OF salary" is included on following line, an error is raised
   CURSOR c1 IS SELECT e.*,rowid FROM employees e;
   emp_rec  employees%ROWTYPE;
BEGIN
   OPEN c1;
   LOOP
     FETCH c1 INTO emp_rec; -- FETCH fails on the second iteration with FOR UPDATE
     EXIT WHEN c1%NOTFOUND;
     IF emp_rec.employee_id = 105 THEN
       UPDATE employees SET salary = salary * 1.05 WHERE rowid = emp_rec.rowid;
         -- this mimics WHERE CURRENT OF c1
     END IF;
     COMMIT;  -- releases locks
   END LOOP;
END;
/

您必须逐行获取记录!立即使用 ROWID AND COMMIT 更新它 .然后继续下一行!

但是通过这个,你不得不放弃Bulk Binding这个选项。

【讨论】:

此代码是动态 SQL,我没有正确捕获异常,这就是我没有收到错误消息的原因。旧代码没有进行批量收集,并且运行了大约 3 天。我使用批量收集重写了它现在运行大约 6 小时。我在重写中错过了“FOR UPDATE”参数,它在代码审查期间被捕获。我已经删除了 COMMIT 这只是意味着该过程将是一个全有或全无的工作,我只在代码中添加了 COMMIT 以便我们可以在必要时停止该工作,而不会丢失已经完成的工作。感谢您的意见。 @Grant 谢谢,这只是承认我的工作问题!

以上是关于使用“更新”批量收集的主要内容,如果未能解决你的问题,请参考以下文章

Oracle:批量收集性能

如何优化使用游标的 PL/SQL 代码

Mybatis批量更新,批量删除

mysql进阶 十四 批量更新与批量更新多条记录的不同值实现方法

我们如何使用 CRecordset 批量更新记录

mysql 批量更新与批量更新多条记录的不同值实现方法