使用表作为SQL 2005中多个进程的工作队列

时间:2010-10-13 09:27:48

标签: sql-server-2005

给定一个包含2列的表格JobTableJobIdJobStatus(还有其他但我将其排除,因为它们对问题没有影响)。< / p>

一个进程, WorkGenerator INSERTs 行进入表中。 另一个进程 Worker 执行名为GetNextJob的存储过程。

现在,GetNextJob执行 SELECT 以查找下一项工作( JobStatus = 1 ),然后执行更新将该作品标记为正在进行中( JobStatus = 2 )。

我们希望通过多个工作人员流程进行扩展,但发现多个工作人员可以选择相同的工作。

我有以下疑问:

  • GetNextJob中,我可以合并吗? SELECT和UPDATE成单个查询 并使用OUTPUT子句来获取 的JobId?
  • 我怎样才能保证这一点 1个过程将拿起每一块 工作?

我很欣赏有效的答案,但也解释了为什么它们有效。

2 个答案:

答案 0 :(得分:2)

答案 1 :(得分:2)

让我们建立一个解决方案:

确保UPDATE检查@@ ROWCOUNT

UPDATE之后检查@@ROWCOUNT以确定哪个工作人员进程获胜。

CREATE PROCEDURE [dbo].[GetNextJob] 
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @jobId INT

    SELECT TOP 1 @jobId = Jobs.JobId FROM Jobs
    WHERE Jobs.JobStatus = 1
    ORDER BY JobId ASC

    UPDATE Jobs Set JobStatus = 2
    WHERE JobId = @jobId
    AND JobStatus = 1;

    IF (@@ROWCOUNT = 1)
    BEGIN
        SELECT @jobId;
    END
END

GO

请注意,使用上述过程,未获胜的进程不会返回任何行,需要再次调用该过程以获取下一行。

上述将解决大多数情况,其中两个工作人员都选择同一工作,因为UPDATE防范了这一点。但是,对于同一个jobId,两个工人的@@ ROWCOUNT可能为1!

在事务中锁定行,因此只有1个工作人员可以更新状态

CREATE PROCEDURE [dbo].[GetNextJob] 
AS
BEGIN
    SET NOCOUNT ON;

    BEGIN TRANSACTION

        DECLARE @jobId INT

        SELECT TOP 1 @jobId = Jobs.JobId FROM Jobs WITH (UPDLOCK, ROWLOCK)
        WHERE Jobs.JobStatus = 1
        ORDER BY JobId ASC

        UPDATE Jobs Set JobStatus = 2
        WHERE JobId = @jobId
        AND JobStatus = 1;

        IF (@@ROWCOUNT = 1)
        BEGIN
            SELECT @jobId;
        END

    COMMIT
END

GO

UPDLOCK和ROWLOCK都是必需的。 SELECT上的UPDLOCK告诉MSSQL锁定行,就像它被更新一样,直到提交事务为止。 ROWLOCK(可能不是必需的)但告诉MSSQL只锁定SELECT返回的ROW。

优化锁定

当1进程使用ROWLOCK提示锁定行时,其他进程将被阻塞,等待释放该锁。可以指定READPAST提示。来自MSDN:

  

指定READPAST时,两者都有   行级和页级锁是   跳过。也就是数据库引擎   而是跳过行或页面   阻止当前交易   直到锁被释放。

这将阻止其他进程被阻止并提高性能。

CREATE PROCEDURE [dbo].[GetNextJob] 
AS
BEGIN
    SET NOCOUNT ON;

    BEGIN TRANSACTION
        DECLARE @jobId INT

        SELECT TOP 1 @jobId = Jobs.JobId FROM Jobs WITH (UPDLOCK, READPAST)
        WHERE Jobs.JobStatus = 1
        ORDER BY JobId ASC

        UPDATE Jobs Set JobStatus = 2
        WHERE JobId = @jobId
        AND JobStatus = 1;

        IF (@@ROWCOUNT = 1)
        BEGIN
            SELECT @jobId;
        END

    COMMIT
END

GO

要考虑:结合SELECT和更新

组合SELECT和UPDATE并使用SET获取ID。

例如:

DECLARE @val int

UPDATE JobTable 
SET @val = JobId, 
status = 2 
WHERE rowid = (SELECT min(JobId) FROM JobTable WHERE status = 1) 

SELECT @val 

这仍然要求事务是SERIALIZABLE以确保每行只分配给一个Worker。

要考虑:再次合并SELECT和UPDATE

组合SELECT和UPDATE并使用Output子句。