试图防止此死锁发生在 SQL 服务器上我的 .NET 代码中

Trying to prevent this Deadlock from happening in my .NET code on SQL Server

在我对某些 C# 代码进行集成测试期间发生死锁,该代码正在针对 SQL 服务器测试我的 SQL 查询。

我有两个测试,因为这两个测试是并行发生的。

测试 1(伪 C# 代码)

using (start a new transaction)
{
    foreach Person in the People-Collection
    {
        Insert this Person into the DB and grab the ID of this inserted person.
        => INSERT INTO [dbo].[People] (...) VALUES (...);
           SELECT CAST(SCOPE_IDENTITY() AS INT);
    }

    Now, count how many people are in the DB right now (because there might have been more, from before we inserted these temp people).
        => SELECT COUNT(PersonId) FROM [dbo].[People]
}
// end transaction (which rolls back because of no explicit transaction.commit)

测试 2

using (start a new transaction)
{
    Update a person
    => UPDATE [dbo].[People]
       SET    .....
       WHERE  PersonId = @personId
}
// end transaction which auto rolls back.

所以我猜测发生了以下情况:

出于某种原因,test1 正在等待 test2 完成更新,但 test2 无法更新(出于我无法想象的原因)。

我认为以下应该发生:

显然,我不明白发生了什么:(

索引 死锁图列出了一个特定的索引作为悲伤的原因。这是索引:

ALTER TABLE [dbo].[People] ADD  CONSTRAINT [IX_People_Name] UNIQUE NONCLUSTERED 
(
    [Name] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

有人可以对我如何解决这个问题提出一些建议吗?

注意:This gist 包含完整的死锁图作为参考。

编辑 1:添加了 using 语句代码以便更好地理解 logic/flow。 编辑 2:添加索引 schema/code

您在名称列上有一个索引,因此出于 locking/IO 目的,更新表现为删除+插入。

死锁图显示 Test1 等待获取 RangeS-S 锁以对所有行进行计数,同时对插入的行持有 RangeX-X 锁。它需要整个 table 的共享锁来计算行数和插入行的独占锁(不确定为什么要使用范围锁)。

同时 Test2 正在等待获取 RangeI-N 锁以插入新名称行,同时在删除的旧名称行上持有 X 锁。

我会建议以下其中一项:

  • 在主键 (PersonID?) 字段上创建一个狭窄的唯一索引 - 该索引不需要锁定更新,可用于快速计数 (*) 并且维护起来非常便宜。它还提高了外键完整性检查的并发性和性能(要使其生效,FK-s 引用 [People] table 必须是 re-created)。
  • 更改计数查询的锁定(如果业务要求允许)- 例如使用 WITH(READCOMMITTED) table 提示或在提交插入后执行此操作。
  • 运行 插入前的计数查询,并按插入的行数递增(这可能对并发性不利,因为它在整个 table 上持有共享锁)