INSERT/UPDATE 存储过程本身死锁

Zap*_*002 4 sql-server deadlock insert update sql-server-2014

我有一个表 (Database2.dbo.OrganizerDataDependencyChange),其中包含有关某些其他表中的行上次更改时间的信息。在这些表中的每一个上,我都有一个触发器,它调用一个存储过程 (Database2.dbo.SaveOrganizerDataDependencyChange),该过程简单地用触发器触发的时间更新 Database2.dbo.OrganizerDataDependencyChange。这些触发器是从 Database1 或 Database2 触发的,因此它们通常是跨数据库调用。

当更新表上的两个不同行时,我在 Database1.dbo.OrganizerDataDependencyChange 表上遇到读写死锁,我不明白为什么。表上只有一个索引,而且它是完全覆盖查询的聚集索引,因此不可能出现查找死锁,尽管 proc 中有两条语句,但我特地将其重写为我所理解的最佳方法避免并发问题,使用 WHERE NOT EXISTS 而不是使用 IF/ELSE 逻辑,所以它不应该从不同的角度出现在这个索引上,对吗?由于它是在触发器和跨数据库中触发的,因此我在重现该问题时遇到了一些困难,我当然可以重现阻塞,但这应该没问题,这正是我所期望的。

有人可以帮助我了解这里发生了什么吗?我可能可以使用 NOLOCK 提示或 applock 修复它,但我仍然不明白为什么会发生这种情况。

这是表:

CREATE TABLE [dbo].[OrganizerDataDependencyChange](
[TableID] [INT] NOT NULL,
[Database] [VARCHAR](150) NOT NULL,
[Updated] [DATETIME] NULL
)
CREATE CLUSTERED INDEX [CX_OrganizerDataDependencyChange_TableID_DB] ON [dbo].[OrganizerDataDependencyChange]
(
[TableID] ASC,
[Database] ASC
)
Run Code Online (Sandbox Code Playgroud)

这是存储过程:

CREATE PROCEDURE [dbo].[SaveOrganizerDataDependencyChange]
(
@TableID int,
@Database varchar(150)
)
AS
BEGIN

SET NOCOUNT ON;

    DECLARE @rowcount INT;

INSERT INTO dbo.OrganizerDataDependencyChange
        ( TableID, [Database], Updated )
SELECT TOP 1
    TableID = @TableID,
    [Database] = ISNULL(@Database, ''),
    Updated = GETDATE()
FROM dbo.OrganizerDataDependencyChange
WHERE NOT EXISTS
(
    SELECT
        1
    FROM dbo.OrganizerDataDependencyChange
    WHERE TableID = @TableID
    AND [Database] = @Database
)

SET @rowcount = @@ROWCOUNT  --How many rows were inserted?

UPDATE dbo.OrganizerDataDependencyChange
SET Updated = GETDATE()
WHERE @rowcount = 0 --Only run this if no rows were inserted
AND TableID = @TableID
AND [Database] = ISNULL(@Database, '')

END
Run Code Online (Sandbox Code Playgroud)

最后,我从 XE 中得到的几个死锁图显示了荒谬的嵌套触发器:

死锁 1

死锁2

MSSQL2014 标准,如果这有区别的话。隔离级别是默认的 Read Committed。如果还有什么我可以分享以阐明更多信息,请告诉我!

Aar*_*and 7

我想你会更好地发现这种模式:

BEGIN TRANSACTION;

UPDATE dbo.OrganizerDataDependencyChange WITH (HOLDLOCK)
  SET Updated = GETDATE()
  WHERE TableID = @TableID
  AND [Database] = ISNULL(@Database, '');

IF @@ROWCOUNT = 0
BEGIN
  INSERT INTO dbo.OrganizerDataDependencyChange
      ( TableID, [Database], Updated )
  VALUES(@TableID, ISNULL(@Database, ''), GETDATE());
END

COMMIT TRANSACTION;
Run Code Online (Sandbox Code Playgroud)

不要再考虑必须“检查”是否有要更新的行,然后再更新它,这可能会导致两次完整扫描。只是尝试更新它。如果没有行被更新,没有伤害,没有犯规 - 您可以简单地执行插入(我在这里稍微讨论一下)。