正确使用数据库锁定方式随机返回尚未使用的单个记录

时间:2015-01-16 15:48:11

标签: sql sql-server sql-server-2008 database-locking

我有一个表有点像队列的表,除了从表中删除记录。我试图尝试的是让存储过程返回一个当前没有从前端应用程序处理的记录。我们所拥有的是一个“锁定”列,我们将其设置为表明这一点。我们这样做的原因是,一次只有一个呼叫中心代理可以处理记录。这是我的sql到目前为止的样子。问题是如果我从两个单独的会话运行此查询(第二个会话注释掉waitfor语句),第二个会话不会返回任何记录10秒。我在选择记录时将其缩小到order by子句。如果我删除Order By它返回但我需要按顺序。

或许我的查询完全错了?我应该使用事务隔离级别(可序列化,快照)??任何指导都会很棒!

DECLARE @WorkItemId INT;

BEGIN TRANSACTION

/* TODO: Skip Records That Have Been Completed. */
SET @WorkItemId = (SELECT TOP 1 CampaignSetDetailId FROM CampaignSetDetail WITH (XLOCK, READPAST) WHERE LockedBy IS NULL ORDER BY NEWID(), NumAttempts ASC);

/* */
UPDATE CampaignSetDetail SET LockedBy = 'MPAUL', LockedDTM = GETUTCDATE() WHERE CampaignSetDetailId = @WorkItemId;

/* */
SELECT * FROM CampaignSetDetail WHERE CampaignSetDetailId = @WorkItemId;

WAITFOR DELAY '00:00:10';

COMMIT TRANSACTION

3 个答案:

答案 0 :(得分:0)

尝试以下操作 - 一组重试尝试,并使用更新锁定。

  DECLARE @RetryCount Int = 1
  DECLARE @MaxRetries Int = 5;
  SET @RetryCount = 1 
  WHILE @RetryCount < @MaxRetries
   BEGIN
      BEGIN TRY

            /* TODO: Skip Records That Have Been Completed. */
         SET @WorkItemId = (
                            SELECT TOP 1
                                    CampaignSetDetailId
                            FROM    CampaignSetDetail WITH (UPDLOCK)
                            WHERE   LockedBy IS NULL
                            ORDER BY NEWID()
                                   ,NumAttempts ASC
                           );

         UPDATE   CampaignSetDetail WITH (UPDLOCK)
         SET      LockedBy = 'MPAUL'
                 ,LockedDTM = GETUTCDATE()
         WHERE    CampaignSetDetailId = @WorkItemId;

         SELECT   *
         FROM     CampaignSetDetail WITH (UPDLOCK)
         WHERE    CampaignSetDetailId = @WorkItemId;

         SELECT   @RetryCount = @MaxRetries;
      END TRY
      BEGIN CATCH
         IF ERROR_NUMBER() IN (1204, 1205, 1222)
            BEGIN
               SET @RetryCount += 1;
               WAITFOR DELAY '00:00:02';
            END 
         ELSE
            THROW;
      END CATCH
   END  

有关特定锁定错误的详细信息,请参阅here。即使从其他会话执行同一表的更新,这也应该有效。

答案 1 :(得分:0)

你可以在一个声明中做到这一点

DECLARE @WorkItemId INT;

UPDATE [CampaignSetDetail]
    SET
            [LockedBy] = 'MPAUL',
            [LockedDTM] = GETUTCDATE()
    WHERE
            [CampaignSetDetailId] = (
                SELECT TOP 1
                            [CampaignSetDetailId],
                            @WorkItemId = [CampaignSetDetailId]
                    FROM
                            [CampaignSetDetail]
                    WHERE
                            [LockedBy] IS NULL
                    ORDER BY
                            [NumAttempts]);

SELECT
            * -- You shouldn't do this.
    FROM
            [CampaignSetDetail]
    WHERE
            [CampaignSetDetailId] = @WorkItemId;

我假设[CampaignSetDetailId]在表格上形成聚集索引。您可以考虑像

这样的索引
CREATE INDEX [IX_CampaignSetDetail_NumAttempts]
    ON [CampaignSetDetail]([NumAttempts])
    WHERE [LockedBy] IS NULL;

优化此操作。

答案 2 :(得分:0)

我认为我找到了一个很好的解决方案,但我仍在评估所有答案。我也在尝试使用CTE,这给了我最好的表现。

DECLARE @WorkItemId INT;

WITH CTE AS (
    SELECT TOP 1 [CampaignSetDetailId], LockedBy FROM [dbo].[CampaignSetDetail] WITH (ROWLOCK, READPAST) WHERE LockedBy IS NULL ORDER BY NEWID(), NumAttempts ASC
)
UPDATE CTE SET LockedBy = 'DIESEL', @WorkItemId = [CampaignSetDetailId]

SELECT @WorkItemId

WAITFOR DELAY '00:00:05';