我有多台计算机,其任务是发送在常见SQL Server上的表中找到的电子邮件。每台计算机通过查看设置为0的状态标志来轮询电子邮件表以查找它可以发送的消息。如果计算机执行了
SELECT * FROM tblEmailQueue where StatusFlag=0
如果它返回一条记录,它会立即将StatusFlag
设置为1
,这会导致另一台计算机轮询同一个表而不能找到该记录。我担心如果两台计算机在更新StatusFlag
之前同时找到记录,则电子邮件将被发送两次。有没有人有关于如何确保只有一台计算机才能获得记录的想法?我知道我可以做桌锁但我宁愿现在必须这样做。
答案 0 :(得分:3)
您可以使用OUTPUT
clause一次更新值并输出更新的行,而不是使用可能导致竞争条件的两个查询。
这将更新statusflag = 0的行并输出所有更新的行;
UPDATE tblEmailQueue
SET statusflag=1
OUTPUT DELETED.*
WHERE statusflag=0;
编辑:如果你选择一行,你可能需要一些订购。由于更新本身无法排序,因此您可以使用公用表表达式来执行更新;
WITH cte AS (
SELECT TOP 1 id, statusflag FROM tblEmailQueue
WHERE statusflag = 0 ORDER BY id
)
UPDATE cte SET statusflag=1 OUTPUT DELETED.*;
答案 1 :(得分:1)
您可以在同一交易中执行选择和发送电子邮件。此外,您可以使用ROWLOCK
提示,在发送电子邮件或为StatusFlag
设置新值之前不要提交事务。这意味着只要您提交事务,nobody(具有提示NOLOCK
或READ UNCOMMITED
隔离级别的exept事务)就可以读取此行。
SELECT * FROM tblEmailQueue WITH(ROWLOCK) where StatusFlag=0
此外,您应该检查隔离级别。对于您的情况,隔离级别应为READ COMMITED
或REPEATABLE READ
。
请参阅有关隔离级别here
答案 2 :(得分:0)
在印第安纳波利斯,我们熟悉种族条件; - )
让我们假设您实际拥有ID字段和StatusFlag字段并创建包含
的存储过程declare @id int
select top 1 @id = id from tblEmailQuaue where StatusFlag=0
if @@rowcount = 1
begin
update tblEmailQuaue set StatusFlag=1 where ID = @id AND StatusFlag=0
if @@rowcount = 1
begin
-- I won the race, continue processing
...
end
end
ADDED
如果你想要的只是select的结果,这样的显式处理不如Joachim的方法。但是这种方法也适用于旧版本的SQL服务器以及其他数据库。
答案 3 :(得分:0)
在您的表tblEmailQueue(比如UserID)中添加另一列,然后尝试提取一封电子邮件,例如
--Let flag an email and assign it to the application who made the request
--@CurrentUserID is an id unique to each application or user and supplied by the application
--or user who made the request, this will also ensures that the record is going to
--the right application and perhaps you can use it for other purpose such as monitoring.
UPDATE tblEmailQueue set UserID = @CurrentUserID, StatusFlag=1 where ID = isnull(
select top 1 ID from tblEmailQueue where StatusFlag=0 order by ID
), 0)
--Lets get an email that had a flag for the current user id
SELECT * FROM tblEmailQueue where StatusFlag=1 and UserID = @CurrentUserID