批量收集使用“更新”

时间:2014-02-07 20:49:07

标签: oracle bulk-collect

使用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”子句会导致行为发生这种变化?有关如何解决此问题的任何建议?我将尝试在流程开始时尝试在表上启动独占事务,但这不是一个想法的解决方案,因为我真的不想锁定处理数据的整个表。

先谢谢你的帮助,

捐赠

2 个答案:

答案 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选项。