我想确保一封电子邮件只发送一次,所以我在Oracle SQL中使用以下语句:
update mytable set mail_sent = 't' where id = ? and mail_sent = 'f'
Run Code Online (Sandbox Code Playgroud)
并检查修改的行数.如果没有修改任何行,则另一个进程首先执行相同的操作并发送邮件.如果修改了1行,我发送邮件.(当然,如果发送邮件失败,我会重置mail_sent.进程崩溃的可能性很小,并且将mail_sent留在't',所以没有邮件发送.我会忍受它.)
我无法说服自己这对竞争条件是安全的(进程1读取'f'并且进程2在进程1写入't'之前读取'f',因此两个进程都认为他们修改了行并发送了2封电子邮件.我将隔离级别设置为SERIALIZABLE以避免问题,但这实际上是必要的,还是没有它我是否安全?
有一组Tom Kyte关于在并发更新期间发生的事情的优秀文章,值得一读:
长话短说,如果两个语句进行并发更新,后者一个:
因此,如果您的第一次更新提交't',则第二次更新将永远不会再次更新此行.你可以检查一下sql%rowcount.
一个简单的测试用例(36和37是这里的两个并发会话):
-- first session updates, locks the row
00:41:44 LKU@sandbox(36)> update mail set mail_sent = 't' where id = 1 and mail_sent = 'f';
1 row updated.
Elapsed: 00:00:00.21
-- second session tries to update the same row, it hangs as the row is locked
00:58:13 LKU@sandbox(37)> update mail set mail_sent = 't' where id = 1 and mail_sent = 'f';
-- first session commits
00:58:27 LKU@sandbox(36)> commit;
Commit complete.
Elapsed: 00:00:00.00
-- no rows updated in second!
00:58:13 LKU@sandbox(37)> update mail set mail_sent = 't' where id = 1 and mail_sent = 'f';
0 rows updated.
Elapsed: 00:00:33.12 -- time of me switching between sqlplus tabs and copy-pasting text here ;)
Run Code Online (Sandbox Code Playgroud)
因此,我可以得出结论,如果您在执行更新后检查会话更新的行数 - 您是安全的.