带范围锁定的SQL Server争用条件问题

时间:2012-07-05 21:12:58

标签: sql-server tsql locking deadlock

我正在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分钟更新租约时间并设置新的租赁票。

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

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

死锁图: enter image description here

表架构(简化): enter image description here

2 个答案:

答案 0 :(得分:1)

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

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

答案 1 :(得分: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;