仅选择光标一次的行

时间:2014-04-10 10:47:42

标签: plsql oracle11g

我有一个PL / SQL的情况,我的程序每分钟被一个作业调用。代码如下

procedure ExecuteProc is
CURSOR c1 IS
    SELECT *
    FROM test6
    where processed = 'N';
BEGIN

--  Make all rows selected in the cursor processed 'Y' but 
--  the cursor below would fail

for rec in c1
loop
--    some proccessing which takes 5 -6 minutes
   delete from test6 where id = rec.id;
end loop;
END;

test6表中的示例数据

id  processed
1    N
2    N
3    N

当第一次调用该过程时,通过光标中的过程拾取id 1,2和3。请注意,除非每个id的处理完成,否则表test6中也存在这些行。这意味着当在下一分钟第二次调用该过程时,该过程再次获取1,2和3。我怎么能避免这个?我正在考虑以某种方式将'处理'字段设置为'Y'(请参阅我在光标失败的代码中的注释)。

请帮忙

2 个答案:

答案 0 :(得分:2)

尝试在删除前锁定该行:

procedure ExecuteProc is
CURSOR c1 IS
SELECT *
FROM test6
where processed = 'N';
BEGIN

--  Make all rows selected in the cursor processed 'Y' but 
--  the cursor below would fail

  for rec in c1
  loop
    if rec.processed <> 'L' then


    --    some proccessing which takes 5 -6 minutes
    update test6 set processed ='L' where id = rec.id; -- lock status
    delete from test6 where id = rec.id;
    end if;
   end loop;
END;

您将确定删除正确的。 但是你必须优化,处理后面的程序调用。

答案 1 :(得分:2)

如果你在11g上运气:-)你可以在光标中使用SELECT FOR UPDATE SKIP LOCKED - 那么你可以保证两个线程在获取时永远不会获得相同的行!

在Oracle中执行此操作的正确方法是高级排队 - 因为这将解决所有问题,提供同步和并行执行以及其他好处......

在Oracle 11之前,没有高级排队(如果你真的想自己编写代码......)你可以这样做: (标记为自治事务的位必须在单独的方法中作为自治事务调用。如果Update返回0行,则可能是因为表为空,或者因为两个线程访问了同一行。所以我们需要一个计算可用行的单独结束条件。

-- Example if you CAN NOT use AQ or UPDATE SKIP LOCKED
LOOP
  -- ### autonomous transaction: (can be in its own method returning the id)
  BEGIN
    -- Mark a single ROW as being in processing...
    UPDATE test6 SET processed = 'L' WHERE processed = 'N' AND ROWNUM = 1 NOWAIT
    RETURNING id INTO l_id;
  EXCEPTION WHEN NO_DATA_FOUND THEN ... -- Can possibly fire if two threads access the same row...
    l_id := NULL;
  END
  -- ### end autonomous transaction;

  IF l_id IS NOT NULL THEN

    --DO BIG PROCESSING

    -- Mark the ROW is processed and DONE!
    UPDATE test6 SET processed = 'Y' WHERE id = l_id;

    COMMIT;
  END;

  SELECT COUNT(*) FROM test6 WHERE processed = 'N' INTO rowsleft;
  EXIT IF rowsleft = 0;
END;