3

在使用 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”语法添加到游标定义中是个好主意,以便另一个进程可以'在读取数据和删除记录之间不更新任何记录。

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

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

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

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

在此先感谢您的帮助,

授予

4

2 回答 2

2

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

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

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

于 2014-02-07T20:57:26.650 回答
1

添加到贾斯汀的解释。

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

消息本身解释了很多!

对于这种Updates,最好创建一个主表的影子副本,并让public synonym指向它。虽然一些批处理 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

文档示例:

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选择。

于 2014-02-07T21:31:49.110 回答