如何保证只有一个进程获取处理任务

时间:2014-05-31 06:37:07

标签: sql sql-server

我有多台计算机,其任务是发送在常见SQL Server上的表中找到的电子邮件。每台计算机通过查看设置为0的状态标志来轮询电子邮件表以查找它可以发送的消息。如果计算机执行了

SELECT * FROM tblEmailQueue where StatusFlag=0

如果它返回一条记录,它会立即将StatusFlag设置为1,这会导致另一台计算机轮询同一个表而不能找到该记录。我担心如果两台计算机在更新StatusFlag之前同时找到记录,则电子邮件将被发送两次。有没有人有关于如何确保只有一台计算机才能获得记录的想法?我知道我可以做桌锁但我宁愿现在必须这样做。

4 个答案:

答案 0 :(得分:3)

您可以使用OUTPUT clause一次更新值并输出更新的行,而不是使用可能导致竞争条件的两个查询。

这将更新statusflag = 0的行并输出所有更新的行;

UPDATE tblEmailQueue 
SET statusflag=1 
OUTPUT DELETED.*
WHERE statusflag=0;

An SQLfiddle to test with

编辑:如果你选择一行,你可能需要一些订购。由于更新本身无法排序,因此您可以使用公用表表达式来执行更新;

WITH cte AS (
  SELECT TOP 1 id, statusflag FROM tblEmailQueue 
  WHERE statusflag = 0 ORDER BY id
)
UPDATE cte SET statusflag=1 OUTPUT DELETED.*;

Another SQLfiddle

答案 1 :(得分:1)

您可以在同一交易中执行选择和发送电子邮件。此外,您可以使用ROWLOCK提示,在发送电子邮件或为StatusFlag设置新值之前不要提交事务。这意味着只要您提交事务,nobody(具有提示NOLOCKREAD UNCOMMITED隔离级别的exept事务)就可以读取此行。

SELECT * FROM tblEmailQueue WITH(ROWLOCK) where StatusFlag=0

此外,您应该检查隔离级别。对于您的情况,隔离级别应为READ COMMITEDREPEATABLE 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