我想确保电子邮件只发送一次,因此我在Oracle SQL中使用以下语句:
update mytable set mail_sent = 't' where id = ? and mail_sent = 'f'
并检查修改的行数。如果没有修改任何行,则另一个进程首先执行相同的操作并发送邮件。如果修改了1行,我发送邮件。 (当然,如果发送邮件失败,我会重置mail_sent。进程崩溃的可能性很小,并且将mail_sent留在't',所以没有邮件发送。我会忍受它。)
我无法说服自己这对竞争条件是安全的(进程1读取'f'并且进程2在进程1写入't'之前读取'f',因此两个进程都认为他们修改了行和2封电子邮件发送。我将隔离级别设置为SERIALIZABLE以避免问题,但这实际上是必要的,还是没有它我是否安全?
答案 0 :(得分:6)
有一组Tom Kyte关于并发更新期间发生的一些优秀文章,值得一读:
长话短说,如果两个语句进行并发更新,后一个:
因此,如果您的第一次更新提交',则第二次更新将永远不会再次更新此行。您可以使用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 ;)
因此,我可以得出结论,如果您在执行更新后检查会话更新的行数 - 您是安全的。
答案 1 :(得分:2)
一种安全的方法是选择要更新的行,该行对该行进行独占锁定,发送电子邮件,然后将记录更新为“t”并提交。
记录的锁定是该方法的精心设计目标。在确认发送电子邮件之前,您不希望表明您已发送电子邮件,否则您需要恢复过程以指示传输实际上已失败。同样,当您开始发送电子邮件的过程时,您不希望其他会话启动该过程。
如果有必要避免长期锁定,那么我建议将该过程分为两个步骤 - 设置一个标志以确认电子邮件传输过程已经开始(实际上我是时间戳),以及再次设置(或设置另一个)以确认传输。这本身并不是一个糟糕的方法,因为它可以监控确认所需的时间,根据我的经验,一些互联网请求可能占应用时间的很大一部分。
答案 2 :(得分:0)
这听起来像交易工作。
BEGIN TRANSACTION
UPDATE mytable
SET mail_sent = 't'
WHERE id = @id
AND mail_sent = 'f'
send the email
IF (@emailSent = 0)
ROLLBACK TRANSACTION
RAISERROR('Email not sent', 1, 16);
ELSE
COMMIT TRANSACTION