[英]Preventing deadlocks in SQL Server
我有一个连接到 SQL Server 2014 数据库的应用程序,该数据库将多行合并为一个。 应用程序运行时没有与此数据库的其他连接。
首先,在特定时间跨度内选择一大块行。 此查询使用与集群查找合并的非集群查找(TIME 列)。
select ...
from FOO
where TIME >= @from and TIME < @to and ...
然后,我们在 c# 中处理这些行并将更改写入单个更新和多个删除,每个块会发生很多次。 这些也使用非聚集索引查找。
begin tran
update FOO set ...
where NON_CLUSTERED_ID = @id
delete FOO where NON_CLUSTERED_ID in (@id1, @id2, @id3, ...)
commit
使用多个并行块运行此程序时,我遇到了死锁。 我尝试使用ROWLOCK
进行update
和delete
,但由于某种原因,这导致了比以前更多的死锁,即使块之间没有重叠。
然后我在update
上尝试TABLOCKX, HOLDLOCK
,但这意味着我不能并行执行我的select
,所以我失去了并行性的优势。
知道如何避免死锁但仍然处理多个并行块吗?
在这种情况下,在我的select
上使用NOLOCK
是否安全,因为块之间没有行重叠? 那么TABLOCKX, HOLDLOCK
只会阻止update
和delete
,对吗?
还是我应该接受会发生死锁并在我的应用程序中重试查询?
UPDATE (附加信息):到目前为止,所有死锁都发生在update
和delete
阶段,在select
中没有。 如果我今天不能解决这个问题,我会尝试获取一些死锁日志(之前没有启用正确的跟踪标志)。
更新:这些是ROWLOCK
发生的两种死锁安排,它们都只指delete
语句和它使用的非聚集索引。 我不确定这些是否与没有任何表提示的死锁相同,因为我无法重现其中任何一个。
询问.xdl 是否还需要其他任何东西,我有点厌倦了附加整个东西。
关于死锁的一般建议:确保您以相同的顺序执行所有操作,即针对不同的进程以相同的顺序获取锁。
您可以在 microsoft.com 上的这篇关于最小化死锁的技术文章中找到相同的建议。 它被列在第一位是有充分理由的。
- 以相同的顺序访问对象。
- 避免交易中的用户交互。
- 保持交易简短,一批。
- 使用较低的隔离级别。
- 使用基于行版本控制的隔离级别。
- 将 READ_COMMITTED_SNAPSHOT 数据库选项设置为 ON 以启用已提交读事务以使用行版本控制。
- 使用快照隔离。
- 使用绑定连接。
卡托提问后更新:
在这里如何以相同的顺序获取锁? 你对他如何改变他的 SQL 来做到这一点有什么建议吗?
无论在什么环境下,死锁总是相同的:两个进程(比如A
和B
)以不同的顺序获取多个锁(比如X
和Y
),因此A
正在等待Y
而B
正在等待X
而A
持有X
B
持有Y
。
它适用于此处,因为DELETE
和UPDATE
语句隐含地获取行或索引范围或表上的锁(取决于引擎认为合适的内容)。
您应该分析您的流程并查看是否存在可以以不同顺序获取锁的情况。 如果这没有显示任何内容,您可以使用 SQL Server Profiler 分析死锁:
要跟踪死锁事件,请将死锁图事件类添加到跟踪中。 此事件类使用有关死锁中涉及的进程和对象的 XML 数据填充跟踪中的 TextData 数据列。 SQL Server Profiler 可以将 XML 文档提取到死锁 XML (.xdl) 文件中,稍后您可以在 SQL Server Management Studio 中查看该文件。 您可以将 SQL Server Profiler 配置为将死锁图事件提取到包含所有死锁图事件的单个文件中,或者提取到单独的文件中。
我会在更新事务中使用sp_getapplock
来防止此代码的多个实例并行运行。 这不会像表锁定提示那样阻塞选择语句。
您仍然应该编写重试逻辑,因为获取锁可能需要一段时间,比超时参数更长。
这就是将更新事务包装到sp_getapplock
中的方式。
BEGIN TRANSACTION;
BEGIN TRY
DECLARE @VarLockResult int;
EXEC @VarLockResult = sp_getapplock
@Resource = 'some_unique_name_app_lock',
@LockMode = 'Exclusive',
@LockOwner = 'Transaction',
@LockTimeout = 60000,
@DbPrincipal = 'public';
IF @VarLockResult >= 0
BEGIN
-- Acquired the lock
update FOO set ...
where NON_CLUSTERED_ID = @id
delete FOO where NON_CLUSTERED_ID in (@id1, @id2, @id3, ...)
END ELSE BEGIN
-- return some error code, so that the caller could retry
END;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
-- handle the error
END CATCH;
选择语句不需要任何更改。
我建议不要使用NOLOCK
,即使您说块中的 ID 不重叠。 有了这个提示,SELECT 查询可以跳过一些正在更改的页面,它可以读取一些页面两次。 这种行为是不可能被容忍的。
请在代码中以这种格式使用 get applock。 存储过程 sp_getapplock 将锁放在应用程序资源上。
EXEC Sp_getapplock @Resource = 'storeprocedurename',@LockMode = 'Exclusive',@LockOwner = 'Transaction',@LockTimeout = 25000
这是非常有帮助的。 请增加 LockTimeout 以减少死锁
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.