试图防止此死锁发生在 SQL 服务器上我的 .NET 代码中
Trying to prevent this Deadlock from happening in my .NET code on SQL Server
在我对某些 C# 代码进行集成测试期间发生死锁,该代码正在针对 SQL 服务器测试我的 SQL 查询。
- 软件:SQLServer 2012
- 数据库:
READ COMMITTED ISOLATION
设置为 ON
我有两个测试,因为这两个测试是并行发生的。
测试 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.
所以我猜测发生了以下情况:
- 测试 1 运行 INSERT/SELECT
- 测试 2 运行更新(等待测试 1 完成)
- Test1 完成但现在运行 SELECT COUNT(..)
出于某种原因,test1 正在等待 test2 完成更新,但 test2 无法更新(出于我无法想象的原因)。
我认为以下应该发生:
- Test1 开始 运行(并且在 TRANSACTION 中)。
- Test2 开始 运行 .. 并注意到有一个事务 运行 所以它要么等待该事务完成 要么 可以弄清楚它是update 与 test1 的事务没有任何关系,因此它会更新。
- 测试 1 最终完成。
显然,我不明白发生了什么:(
索引
死锁图列出了一个特定的索引作为悲伤的原因。这是索引:
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 上持有共享锁)
在我对某些 C# 代码进行集成测试期间发生死锁,该代码正在针对 SQL 服务器测试我的 SQL 查询。
- 软件:SQLServer 2012
- 数据库:
READ COMMITTED ISOLATION
设置为ON
我有两个测试,因为这两个测试是并行发生的。
测试 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.
所以我猜测发生了以下情况:
- 测试 1 运行 INSERT/SELECT
- 测试 2 运行更新(等待测试 1 完成)
- Test1 完成但现在运行 SELECT COUNT(..)
出于某种原因,test1 正在等待 test2 完成更新,但 test2 无法更新(出于我无法想象的原因)。
我认为以下应该发生:
- Test1 开始 运行(并且在 TRANSACTION 中)。
- Test2 开始 运行 .. 并注意到有一个事务 运行 所以它要么等待该事务完成 要么 可以弄清楚它是update 与 test1 的事务没有任何关系,因此它会更新。
- 测试 1 最终完成。
显然,我不明白发生了什么:(
索引 死锁图列出了一个特定的索引作为悲伤的原因。这是索引:
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 上持有共享锁)