【问题标题】:SQLServer deadlockSQLServer 死锁
【发布时间】:2009-05-08 08:37:27
【问题描述】:

我有一个 Java 应用程序,它在数据库上执行多个并发 CRUD 操作。我正在添加对 SQLServer 的支持,但在并发删除期间遇到死锁问题。经过一些调查,问题似乎是由于特定表上的锁升级。

为了修复它,我决定使用 UPDLOCK 提示对有问题的表进行“更新”的所有读取,这样可以避免死锁。但是,我仍然看到了问题。我在 SQLServer 中启用了跟踪,并在 SQLServer 日志中发现了以下死锁跟踪:

遇到死锁 .... 打印死锁信息 等待图

节点:1 密钥:5:72057594042384384(54048e7b3828)CleanCnt:3 模式:X 标志:0x0 拨款清单 1: 所有者:0x03D08C40 模式:X Flg:0x0 参考:0 寿命:02000000 SPID:62 ECID:0 XactLockInfo:0x04834274 SPID:62 ECID:0 语句类型:DELETE 行号:1 输入 Buf:语言事件:(@P0 nvarchar(4000))delete from part_data where part_id = @P0 被要求: ResType:LockOwner Stype:'OR'Xdes:0x04B511C8 Mode:U SPID:60 BatchID:0 ECID:0 TaskProxy:(0x058BE378) Value:0x3d08500 Cost:(0/1296)

节点:2

KEY: 5:72057594042384384 (f903d6d6e0ac) CleanCnt:2 模式:X 标志: 0x0 赠款清单 0: 所有者:0x03D088A0 模式:X Flg:0x0 参考:0 寿命:02000000 SPID:60 ECID:0 XactLockInfo:0x04B511EC SPID:60 ECID:0 语句类型:DELETE 行号:1 输入 Buf:语言事件:(@P0 nvarchar(4000))delete from part_data where part_id = @P0 被要求: ResType:LockOwner Stype:'OR'Xdes:0x04834250 Mode:U SPID:62 BatchID:0 ECID:0 TaskProxy:(0x047BA378) Value:0x3d089e0 Cost:(0/4588)

受害者资源所有者: ResType:LockOwner Stype:'OR'Xdes:0x04B511C8 Mode:U SPID:60 BatchID:0 ECID:0 TaskProxy:(0x058BE378) Value:0x3d08500 Cost:(0/1296)

SQLServer 分析器将此显示为两个客户端持有更新 (U) 锁并尝试升级为独占 (X) 锁。我读过的 SQLServer 文档说,在给定时间,只有一个客户端可以在表上拥有 (U) 锁,所以我想知道为什么我会看到跟踪中显示的情况。

该跟踪中引用的数据库对象是外键上的索引。如果有解决此类问题经验的人可以提供建议,那将是一个很大的帮助。

谢谢, 布拉德。

EDIT 按要求添加了死锁图 xml:

<deadlock-list>
 <deadlock victim="process989018">
  <process-list>
   <process id="process6aa7a8" taskpriority="0" logused="4844" waitresource="KEY: 5:72057594042384384 (5504bdfb7529)" waittime="9859" ownerId="613553" transactionname="implicit_transaction" lasttranstarted="2009-05-08T11:52:39.137" XDES="0x5fcbc30" lockMode="U" schedulerid="1" kpid="3516" status="suspended" spid="59" sbid="0" ecid="0" priority="0" transcount="2" lastbatchstarted="2009-05-08T11:52:39.183" lastbatchcompleted="2009-05-08T11:52:39.183" clientapp="jTDS" hostname="LOIRE" hostpid="123" loginname="sa" isolationlevel="read committed (2)" xactid="613553" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128058">
    <executionStack>
     <frame procname="adhoc" line="1" stmtstart="40" sqlhandle="0x0200000007c76c39efdd8317c6fa7b611b4fd958f05cfcf4">
delete from part_data where part_id =  @P0     </frame>
    </executionStack>
    <inputbuf>(@P0 nvarchar(4000))delete from part_data where part_id = @P0</inputbuf>
   </process>
   <process id="process989018" taskpriority="0" logused="1528" waitresource="KEY: 5:72057594042384384 (5e0405cb0377)" waittime="1250" ownerId="613558" transactionname="implicit_transaction" lasttranstarted="2009-05-08T11:52:39.183" XDES="0x48318f0" lockMode="U" schedulerid="2" kpid="2692" status="suspended" spid="60" sbid="0" ecid="0" priority="0" transcount="2" lastbatchstarted="2009-05-08T11:52:39.183" lastbatchcompleted="2009-05-08T11:52:39.183" clientapp="jTDS" hostname="LOIRE" hostpid="123" loginname="sa" isolationlevel="read committed (2)" xactid="613558" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128058">
    <executionStack>
     <frame procname="adhoc" line="1" stmtstart="40" sqlhandle="0x0200000007c76c39efdd8317c6fa7b611b4fd958f05cfcf4">
delete from part_data where part_id =  @P0     </frame>
    </executionStack>
    <inputbuf>(@P0 nvarchar(4000))delete from part_data where part_id =  @P0</inputbuf>
   </process>
  </process-list>
  <resource-list>
   <keylock hobtid="72057594042384384" dbid="5" objectname="MESSAGESTOREDB61.dbo.part_data" indexname="idx_part_data_part_id" id="lock3cab740" mode="X" associatedObjectId="72057594042384384">
    <owner-list>
     <owner id="process6aa7a8" mode="X"/>
    </owner-list>
    <waiter-list>
     <waiter id="process989018" mode="U" requestType="wait"/>
    </waiter-list>
   </keylock>
   <keylock hobtid="72057594042384384" dbid="5" objectname="MESSAGESTOREDB61.dbo.part_data" indexname="idx_part_data_part_id" id="lock3cad340" mode="X" associatedObjectId="72057594042384384">
    <owner-list>
     <owner id="process989018" mode="X"/>
    </owner-list>
    <waiter-list>
     <waiter id="process6aa7a8" mode="U" requestType="wait"/>
    </waiter-list>
   </keylock>
  </resource-list>
 </deadlock>
</deadlock-list>

【问题讨论】:

  • 你在某处有级联删除吗?
  • 嗨斯特凡。不,我没有任何级联删除约束。
  • 的网页屏幕格式没关系,复制/粘贴到本地的.XML文件并打开它会很好看
  • 您是否在使用围绕此删除的事务?如果是这样,交易中是否还有其他事情发生?
  • 是的,声明了一个事务,以确保在出现错误时删除整个对象图或保持原样。事务中没有其他数据库操作,一些读取在事务启动之前完成,但内部没有。

标签: sql-server spring jdbc transactions deadlock


【解决方案1】:

欢迎来到糟糕。

上次我遇到这种情况是因为更新或删除无法找到一个好的索引来帮助它隔离它正在影响的行。这导致了不稳定的锁定升级,因为它使用非覆盖索引来修改定位记录。

因此,如果您可以隔离一些查询,请检查它们的 sql,看看您是否无法尝试提供一些覆盖索引。

覆盖索引是包含特定 where 子句中所有字段的索引。

【讨论】:

  • 没有覆盖索引会有所帮助,但一个好的覆盖索引会包含所有应该返回的列。如果您执行 select cola, colb from table1... cola 和 colb 应该在覆盖索引中,因此 SQL Server 不必返回原始行来获取数据。
【解决方案2】:

SQLServer 上的死锁几乎总是源于单个线程尝试使用两个连接和两个事务进行写入和读取的事实。如果您想完成这项工作,请使用 ONE 连接在单个线程中执行所有操作,并绝对确保您确实在重新使用该连接。这可能是由于您的应用程序中的分层,您在事务期间意外使用不同的连接进行读取,这使得读取等待其他代码(使用另一个连接)完成,因为读取永远不会完成,因此永远不会发生。

示例(伪步骤)

开始翻译

  • 向表 Foo 写入数据(例如 INSERT、UPDATE)
  • 使用 不同 连接从 Foo 读取一些数据(触发表扫描) 死锁,因为读取永远不会完成,所以事务永远不会提交

【讨论】:

  • 弗兰斯,这是一个有趣的观点。 JDBC 的东西是通过 Spring JDBC 模板完成的,所以我自己实际上并没有处理任何连接,但有可能在更新或删除旁边的事务中完成读取。我要回去看看你描述的那种情况
  • 经过进一步调查,我注意到其中一个可疑交易确实在删除中混入了插入。我的应用程序使用四个服务类进行数据库访问,将操作分组为读取、更新、插入和删除。我想问题可能是插入使用与删除不同的连接,因为它在单独的 JDBC 模板类中。我以为 Spring 事务管理会强制操作使用单个连接,也许我错了?
  • 我从未使用过 Spring,所以我不知道它是否会重用正在进行的事务,我可以想象它会,但我也可以想象它不会(例如,如果你连接到不同的数据库,它必须创建一个新的)
【解决方案3】:

首先不要使用提示,通常 SQL Server 最好单独使用。

其次验证您没有真正的死锁(但它发生的频率要低得多,死锁的出现可以暗示)。

第三,正如有人建议的那样,检查您是否有一些缓慢的查询并对其进行调整。

根据我的经验每次都有客户报告出现死锁情况,这是因为运行缓慢的查询会升级,而从来没有真正的死锁并调整查询或添加特定索引 总是解决了问题。

我看不出您说的是哪个版本的 SQL Server,但每个后续版本都比前一个版本具有更好的死锁管理,而且 SQL Server 2000 在这个主题中尤其令人讨厌。

问候
马西莫

【讨论】:

  • 您好 Massimo,感谢您的回答。目前有点忙,但我会尽快重新访问并回复您。
【解决方案4】:

我假设您运行了类似的内容:DBCC TRACEON(1222,-1)”和/或“DBCC TRACEON(1204,-1)。我发现 SQLServer 日志中的死锁跟踪很难阅读。您确定这是升级为独占 (X) 锁的尝试吗?

尝试在分析器中运行跟踪(选择空白模板),选择“死锁图事件”,然后在出现的新选项卡上(事件提取设置),保存每个(选中“分别保存死锁 XML 事件") 在自己的文件中。在 xml 查看器中打开这个文件,很容易知道发生了什么。每个进程都包含在内,带有过程/触发器/等调用的执行堆栈等,所有锁也都在其中。我很难相信 col=@value 的两个 DELETE 导致了问题,看看执行堆栈,是否有触发器或其他事情发生?

查看文件的“资源列表”部分,它将显示导致死锁的每个进程正在锁定和持有的内容。弄清楚如何不锁定其中之一,这样死锁就会得到解决。

【讨论】:

  • 您好,感谢您的评论。我以前使用过探查器,但现在我可以看到我错误地解释了输出。从 XML 资源列表看来,两个客户端已经拥有 (X) 锁,实际上正在请求 (U) 锁。从执行堆栈中,唯一显示的是两个删除语句。
  • 您在每个过程中尝试进行多少删除操作?每个过程有多个还是只有一个?如果您在每个进程的循环中(使用事务)进行多次删除,那么您可能会遇到锁定问题,因为锁定不是在行上,而是在多行的页面上。请编辑您的问题并包含死锁图 xml,以便我可以更好地查看您所看到的内容。
  • 事务正在对对象图执行删除,这些对象组合在一起构成数据的逻辑单元。删除失败的表对应于该图中最底部的对象。这些对象可能不止一个,但在此级别仅执行一次删除,因为它是“删除外键=?”。我仍然很好奇为什么死锁发生在索引而不是表本身。
  • 您是否处于一次删除一行的循环中?
  • 哪张表中的一行?如果您的意思是出现死锁的表,那么没有。根据我的最后一条评论,它是外键列上的“删除位置”匹配。
【解决方案5】:

...我决定阅读所有内容 有问题的表要“更新” 使用 UPDLOCK 提示,以便 可以避免死锁...

...是的,有一个交易声明 确保整个对象图是 删除或保持原样应该有 一个错误。没有其他数据库 事务中的操作,一些 读取在事务之前完成 已启动,但里面没有...

我不确定我是否理解在事务之外升级读锁的原因,我认为这只会加剧问题。我的经验是,先发制人地指定锁定提示弊大于利。

话说回来……

听起来您正试图删除每个事务中的多行,并且正在发生死锁,因为 SQL 正在升级每个事务的表上的锁定级别。请记住,在 SQL Server 行中,键范围和页面锁都立即升级为表锁。如果您有多个事务删除多行,他们会尝试升级,您会遇到死锁。

如果您有很多用户,这会降低性能,但请尝试在您的删除语句中指定 TABLOCK 提示并查看问题是否消失。

还可以查看 SQL 语句的执行计划,了解锁升级是如何发生的。

【讨论】:

    【解决方案6】:

    多年前,我在一张以每天超过 10 亿笔交易为目标的桌子上遇到了同样的问题。

    我们有四个 tomcat 服务来处理一个网站的查询。 有时,两个不同的服务试图处理同一个查询,当网站流量最低时出现。 第一个查询的删除操作锁定了 IX,而同时执行的第二个查询也在做同样的事情。

    我们通过创建缓冲表来解决这个问题。删除操作存储在该表中。 每秒钟,一个 sql 作业读取该表以删除标记为“待删除”的行。

    【讨论】:

      【解决方案7】:

      首先是两个删除语句。这两个 SPID 正在执行相同的语句

      delete from part_data where part_id = @P0

      他们可能不会尝试删除相同的记录。这里发生了死锁,因为 SQLServer 无法使用列 part_id 识别表 part_data 中的行/行,并且它还锁定了其他记录。

      所以你有两个选择

      1. part_id设为主键(如果可能)

      2. 如下修改语句。 (假设有主键)

        Select primary_key into #temp From part_data where part_id = @p0

        --你会得到要删除的记录的主键

        delete a from part_data a join #temp b on a.primary_key = b.primary_key

      -- 可以删除要删除的记录而不锁定其他记录

      【讨论】:

        猜你喜欢
        • 2017-11-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-12-27
        相关资源
        最近更新 更多