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