防止SQL Server中出现死锁

时间:2016-11-14 09:25:24

标签: sql sql-server sql-server-2014 deadlock

我有一个连接到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用于updatedelete,但由于某些原因导致死锁比以前更多,即使块之间没有重叠。

然后我在TABLOCKX, HOLDLOCK上尝试update,但这意味着我无法并行执行select,因此我失去了并行的优势。

知道我怎么能避免死锁但仍处理多个并行块?

在这种情况下,NOLOCK使用select是否安全,因为块之间没有行重叠?那么TABLOCKX, HOLDLOCK只能屏蔽updatedelete,对吗?

或者我应该接受死锁会发生并在我的应用程序中重试查询?

更新(其他信息):到目前为止,所有死锁都发生在updatedelete阶段,select中没有。如果我今天无法解决这个问题(我之前没有启用正确的跟踪标志),我会尝试获取一些死锁日志。

UPDATE :这是ROWLOCK发生的两种死锁安排,它们都只引用delete语句及其使用的非聚集索引。我不确定这些是否与没有任何表提示的死锁相同,因为我无法重现任何这些。

Deadlock 1 Deadlock 2

询问.xdl是否需要其他任何东西,我有点厌倦了整个事情。

2 个答案:

答案 0 :(得分:1)

关于死锁的一般建议:确保以相同的顺序执行所有操作,即以相同的顺序获取锁,以用于不同的进程。

您可以在microsoft.com上关于Minimizing Deadlocks的技术文章中找到相同的建议。有一个很好的理由它首先列出。

  
      
  • 以相同的顺序访问对象。
  •   
  • 避免交易中的用户互动。
  •   
  • 保持交易简短且一批。
  •   
  • 使用较低的隔离级别。
  •   
  • 使用基于行版本控制的隔离级别。
  •   
  • 将READ_COMMITTED_SNAPSHOT数据库选项设置为ON以启用读取提交的事务以使用行版本控制。
  •   
  • 使用快照隔离。
  •   
  • 使用绑定连接。
  •   

Cato提问后更新:

  

如何以相同的顺序获取锁定?你有什么建议他会改变他的SQL吗?

无论在什么环境下,死锁总是相同的:两个进程(比如A&amp; B)获取多个锁(比如X&amp; Y)in一个不同的顺序,以便A等待YB等待XA持有XB }持有Y

它适用于此处,因为DELETEUPDATE语句隐含地获取行或索引范围或表上的锁(取决于引擎认为合适的内容)。

您应该分析您的流程,看看是否存在可以按不同顺序获取锁定的情况。如果没有透露任何内容,您可以analyze deadlocks using the SQL Server Profiler

  

要跟踪死锁事件,请将死锁图事件类添加到跟踪中。此事件类使用有关死锁中涉及的进程和对象的XML数据填充跟踪中的TextData数据列。 SQL Server Profiler可以将XML文档提取到死锁XML(.xdl)文件,稍后可以在SQL Server Management Studio中查看该文件。您可以配置SQL Server Profiler以将死锁图事件提取到包含所有死锁图事件的单个文件,或者分离文件。

答案 1 :(得分:0)

我在更新事务中使用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查询可以跳过某些正在更改的页面,它可以读取一些页面两次。这种行为不太可能被容忍。