繁体   English   中英

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

[英]Correct way to use database locking to randomly return single record not already in use

我有一个表,它的行为有点像队列,只是记录从未从表中删除。 我试图尝试的是让存储过程返回一条当前未从前端应用程序处理的记录。 我们拥有的是一个“锁定”列,我们将其设置为表明这一点。 我们这样做的原因是,一次只能有一个呼叫中心座席可以处理一条记录。 到目前为止,这是我的sql的样子。 问题是如果我从两个单独的会话中运行此查询(第二个会话注释了waitfor语句),第二个会话在10秒钟内未返回任何记录。 选择记录时,我将其范围缩小到order by子句。 如果删除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

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

  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  

有关特定锁定错误的更多详细信息,请参见此处 即使从其他会话执行了同一表上的更新,这也应该起作用。

您可以一句话做

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;

优化此操作。

我认为我找到了一个好的解决方案,但我仍在评估所有答案。 我也在尝试使用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';

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM