使用“更新”批量收集
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 谢谢,这只是承认我的工作问题!以上是关于使用“更新”批量收集的主要内容,如果未能解决你的问题,请参考以下文章