2014-02-07 41 views
3

在使用BULK COLLECT处理Oracle(11g)中的记录时,遇到了一个有趣且意外的问题。使用“for update”进行批量收集

下面的代码正在运行大,处理过的所有万美元,外加记录用了一个问题:

-- 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),我们只是删除记录读取和处理,我认为将“更新”语法添加到游标定义是一个好主意,以便另一个进程在数据读取和记录被删除的时间之间无法更新任何记录。

所以,我改变了代码的唯一事情是......

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

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

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

预先感谢您的帮助,

格兰特

回答

1

的问题是,你正在试图做跨犯取。

当您打开My_Data_Curfor update条款,Oracle必须锁定在My_Data_1表中的每一行,才可以返回任何行。当你commit时,Oracle必须释放所有这些锁(Oracle创建的锁不会跨越事务)。由于游标不再具有您请求的锁,因此Oracle必须关闭游标,因为它不再符合for update子句。因此,第二次提取必须返回0行。

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

+0

贾斯汀 - 感谢您的快速回复,我很欣赏你列出了几个选项的事实! – Grant

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

+1

此代码是动态SQL,我没有正确捕获异常,这就是为什么我没有收到错误消息。旧代码没有进行批量收集,并且运行了大约3天。我使用批量收集重写了它,现在它在大约6个小时内运行。我在重写时错过了“FOR UPDATE”参数,并在代码审查期间被捕获。我已经删除了COMMIT,这意味着这个过程将是一个全部或没有任何工作,我只在代码中添加了COMMIT,所以我们可以在必要时停止这项工作,而不是放松已经完成的工作。感谢您的输入。 – Grant

+0

@Grant谢谢,这只是承认我的工作问题与我! –