我有一个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'(请参阅我在光标失败的代码中的注释)。
请帮忙
答案 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;