事务发生死锁 – 尝试“锁定”记录以进行处理

huangapple go评论63阅读模式
英文:

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.

huangapple
  • 本文由 发表于 2023年3月7日 01:33:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/75654029.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定