2

我正在 SQL Server 中实现一个队列(请不要对此进行讨论)并且遇到了竞争条件问题。感兴趣的 T-SQL 如下:

set transaction isolation level serializable
begin tran
declare @RecordId int
declare @CurrentTS datetime2
set @CurrentTS=CURRENT_TIMESTAMP
select top 1 @RecordId=Id from QueuedImportJobs with (updlock) where Status=@Status and (LeaseTimeout is null or @CurrentTS>LeaseTimeout) order by Id asc
if @@ROWCOUNT> 0
begin
update QueuedImportJobs set LeaseTimeout = DATEADD(mi,5,@CurrentTS), LeaseTicket=newid() where Id=@RecordId
select * from QueuedImportJobs where Id = @RecordId
end
commit tran

RecordId是PK,还有一个索引Status,LeaseTimeout

我基本上在做的是选择一条租约恰好过期的记录,同时将租约时间更新为 5 分钟并设置新的租约票。

所以问题是当我使用几个线程并行运行这段代码时,我遇到了死锁。我已经对其进行了调试,直到发现更新语句有时会为同一条记录执行两次。现在,我的印象是with (updlock)应该防止这种情况(顺便说一句,它也会发生xlock,而不是tablockx)。所以实际上看起来在同一范围的记录上有一个 RangeS-U 和一个 RangeX-X 锁,这应该是不可能的。

那么我错过了什么?我在想它可能与 top 1 子句有关,或者 SQL Server 不知道它where Id=@RecordId实际上在锁定范围内?

死锁图: 在此处输入图像描述

表架构(简化): 在此处输入图像描述

4

2 回答 2

1

看起来锁在不同的 HOBT 上。表上有多个索引吗?

如果是这样,则选择with (updlock)可能只update锁定一个索引。

于 2012-07-05T21:14:57.337 回答
1

为什么不只是:

DECLARE @t TABLE(Id INT);

UPDATE TOP (1) dbo.QueuedImportJobs 
  SET LeaseTimeout = DATEADD(MINUTE, 5, CURRENT_TIMESTAMP)
  OUTPUT inserted.Id INTO @t
  WHERE Status = @Status 
  AND COALESCE(LeaseTimeout, '19000101') < CURRENT_TIMESTAMP;

SELECT <cols> FROM dbo.QueuedImportJobs 
  WHERE Id IN (SELECT Id FROM @t);

顺便说一句,您可能需要ORDER BY根据所需的索引顺序确保所选行是队列中的第一个。如果 Id 上的索引是聚集的,这可能就是它无论如何都会工作的方式,但除非你这么说,否则无法保证。这将需要对查询进行轻微的重组,因为您不能ORDER BY直接在 上应用(或索引提示)UPDATE,例如:

WITH x AS
(
  SELECT TOP (1) Id, LeaseTimeout
    FROM dbo.QueuedImportJobs
    WHERE Status = @Status 
    AND COALESCE(LeaseTimeout, '19000101') < CURRENT_TIMESTAMP
    ORDER BY Id
)
UPDATE x
  SET LeaseTimeout = DATEADD(MINUTE, 5, CURRENT_TIMESTAMP)
  OUTPUT inserted.id INTO @t;
于 2012-07-05T21:19:08.480 回答