我正在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
实际上处于锁定范围内?
死锁图:
表架构(简化):
答案 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;