英文:
Transaction was deadlocked - attempting to 'lock' records for processing
问题
以下是您提供的代码的翻译:
我有以下存储过程,被多个工作服务的实例调用。当服务首次启动时,表上会出现争用,我会遇到死锁问题,需要解决:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRAN
DECLARE @Inserted TABLE (
[Id] BIGINT NOT NULL PRIMARY KEY,
[Status] INT NOT NULL,
[LockDate] DATETIME NULL,
[InternalId] BIGINT NULL,
[SourceUpdated] BIT NOT NULL
)
UPDATE tm
SET
tm.LockDate = DATEADD(MINUTE, @LockDuration, GETDATE()), [ModifiedDate] = GETDATE()
OUTPUT
Inserted.[Id],
Inserted.[Status],
Inserted.LockDate,
Inserted.InternalId,
Inserted.SourceUpdated
INTO @Inserted
FROM [Transactional].[Message] tm WITH (UPDLOCK)
INNER JOIN
(SELECT TOP 250 [Id] FROM [Transactional].[Message]
WHERE [Status] = 0 AND LockDate IS NULL
ORDER by [ModifiedDate] ASC) tmc on tmc.Id = tm.Id
COMMIT TRAN
SELECT
[Id],
[Status],
[LockDate],
[InternalId],
[SourceUpdated]
FROM
@Inserted
这段代码的目标是为每个进程锁定 250 条记录。如果需要,可以考虑Entity Framework是否可能是导致问题的因素:
var records = await _context.TransactionalMessages!
.FromSqlRaw($"EXEC [Records].[LockRecordsForProcessing]")
.ToListAsync(cancellationToken: token);
我收到的错误消息是:
Microsoft.Data.SqlClient.SqlException (0x80131904): 事务 (进程 ID 91) 在锁资源上与另一个进程发生死锁,并已选择作为死锁牺牲者。重新运行事务。
英文:
I have the following stored procedure that is being called by a number of instances of a a worker service. When the services first start, there is contention on the table and I get deadlocks and need to get rid;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRAN
DECLARE @Inserted TABLE (
[Id] BIGINT NOT NULL PRIMARY KEY,
[Status] INT NOT NULL,
[LockDate] DATETIME NULL,
[InternalId] BIGINT NULL,
[SourceUpdated] BIT NOT NULL
)
UPDATE tm
SET
tm.LockDate = DATEADD(MINUTE, @LockDuration, GETDATE()), [ModifiedDate] = GETDATE()
OUTPUT
Inserted.[Id],
Inserted.[Status],
Inserted.LockDate,
Inserted.InternalId,
Inserted.SourceUpdated
INTO @Inserted
FROM [Transactional].[Message] tm WITH (UPDLOCK)
INNER JOIN
(SELECT TOP 250 [Id] FROM [Transactional].[Message]
WHERE [Status] = 0 AND LockDate IS NULL
ORDER by [ModifiedDate] ASC) tmc on tmc.Id = tm.Id
COMMIT TRAN
SELECT
[Id],
[Status],
[LockDate],
[InternalId],
[SourceUpdated]
FROM
@Inserted
The objective here is to lock 250 records for each process. The code is called from Entity Framework if that could be a factor;
var records = await _context.TransactionalMessages!
.FromSqlRaw($"EXEC [Records].[LockRecordsForProcessing]")
.ToListAsync(cancellationToken: token);
The error I get is;
Microsoft.Data.SqlClient.SqlException (0x80131904): Transaction (Process ID 91) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
答案1
得分: 2
将UPDLOCK添加到前250个子查询中。 SERIALIZABLE默认情况下以S锁进行读取,这将导致死锁。 基本上,SERIALIZABLE通过创建死锁来强制实现隔离。
所以你要么需要比SERIALIZABLE更严格的锁定,要么在发生死锁时需要重试。
英文:
Add UPDLOCK to the TOP 250 subquery too. SERIALIZABLE by default reads with S locks, which will lead to deadlocks. Basically SERIALIZABLE enforces isolation by creating deadlocks.
So you either need more restrictive locking than SERIALIZABLE, or you need to retry on deadlock.
答案2
得分: 2
我认为这是一个不太好的设计,但以下代码应该能够在不使用显式事务的情况下消除死锁:
DECLARE @OP TABLE
(
[Id] bigint NOT NULL PRIMARY KEY,
[Status] int NOT NULL,
[LockDate] datetime NULL,
[InternalId] bigint NULL,
[SourceUpdated] bit NOT NULL
);
WITH Msg
AS
(
SELECT TOP (250) Id, [Status], LockDate, InternalId, SourceUpdated
,ModifiedDate
FROM Transactional.[Message] WITH (UPDLOCK, SERIALIZABLE)
WHERE [Status] = 0 AND LockDate IS NULL
ORDER BY ModifiedDate
)
UPDATE Msg
SET LockDate = DATEADD(minute, @LockDuration, CURRENT_TIMESTAMP)
,ModifiedDate = CURRENT_TIMESTAMP
OUTPUT Inserted.[Id], Inserted.[Status], Inserted.LockDate
,Inserted.InternalId, Inserted.SourceUpdated
INTO @OP;
SELECT Id, [Status], LockDate, InternalId, SourceUpdated
FROM @OP;
如果你仍然有很多阻塞,并且希望保留这种设计模式,你还可以考虑将Transactional.[Message] 表设置为内存表(In-Memory table)。
英文:
I think this is poor design but the following should get rid of the deadlocks without an explicit transaction:
DECLARE @OP TABLE
(
[Id] bigint NOT NULL PRIMARY KEY,
[Status] int NOT NULL,
[LockDate] datetime NULL,
[InternalId] bigint NULL,
[SourceUpdated] bit NOT NULL
);
WITH Msg
AS
(
SELECT TOP (250) Id, [Status], LockDate, InternalId, SourceUpdated
,ModifiedDate
FROM Transactional.[Message] WITH (UPDLOCK, SERIALIZABLE)
WHERE [Status] = 0 AND LockDate IS NULL
ORDER BY ModifiedDate
)
UPDATE Msg
SET LockDate = DATEADD(minute, @LockDuration, CURRENT_TIMESTAMP)
,ModifiedDate = CURRENT_TIMESTAMP
OUTPUT Inserted.[Id], Inserted.[Status], Inserted.LockDate
,Inserted.InternalId, Inserted.SourceUpdated
INTO @OP;
SELECT Id, [Status], LockDate, InternalId, SourceUpdated
FROM @OP;
If you still have a lot of blocking, and want to keep this design pattern, you could also look at making Transactional.[Message] an In-Memory table.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论