使用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”子句会导致行为发生这种变化?有关如何解决此问题的任何建议?我将尝试在流程开始时尝试在表上启动独占事务,但这不是一个想法的解决方案,因为我真的不想锁定处理数据的整个表。
先谢谢你的帮助,
捐赠
答案 0 :(得分:1)
问题是你试图在提交中进行提取。
当您使用My_Data_Cur
子句打开for update
时,Oracle必须先锁定My_Data_1
表中的每一行,然后才能返回任何行。当您commit
时,Oracle必须释放所有这些锁(Oracle创建的锁不会跨越事务)。由于游标不再具有您请求的锁,因此Oracle必须关闭游标,因为它不再满足for update
子句。因此,第二次提取必须返回0行。
最合乎逻辑的方法几乎总是删除commit
并在单个事务中完成整个事务。如果你真的,真的,真的需要单独的事务,你需要为循环的每次迭代打开和关闭游标。最有可能的是,你想要做一些事情来限制游标每次打开时只返回100行(即一个rownum <= 100
子句),这样你就不会花费访问每一行来放置锁定,然后锁定除处理和删除的100之外的每一行,以便每次循环都释放锁。
答案 1 :(得分:1)
加入贾斯汀的解释。
您应该看到以下错误消息。不确定,如果您的Exception
处理程序抑制了此错误。
消息本身解释了很多!
对于这种更新,最好创建主表的卷影副本,并让公共同义词指向它。虽然有一些批处理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
选项。