SQLServer死锁

时间:2009-05-08 08:37:28

标签: sql-server spring jdbc transactions deadlock

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

为了解决这个问题,我决定使用UPDLOCK提示对所讨论的表进行所有读取“for update”,以便可以避免死锁。但是,我仍然看到了这个问题。我在SQLServer中启用了跟踪,并在SQLServer日志中找到了以下死锁跟踪:

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

节点:1 KEY: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))从part_data中删除part_id = @ P0 被要求: ResType:LockOwner Stype:'OR'Xdes:0x04B511C8模式:U SPID:60 BatchID:0 ECID:0 TaskProxy:(0x058BE378)值:0x3d08500成本:(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))从part_data中删除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模式:U SPID:60 BatchID:0 ECID:0 TaskProxy:(0x058BE378)值:0x3d08500成本:(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>

7 个答案:

答案 0 :(得分:4)

欢迎来到可怕的。

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

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

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

答案 1 :(得分:2)

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

示例(伪步骤)

开始转化

  • 将数据(例如INSERT,UPDATE)写入表Foo
  • 使用不同的连接从Foo读取一些数据(触发表扫描) DEADLOCK,因为读取永远不会完成,所以transactio永远不会被提交

答案 2 :(得分:1)

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

Secondo验证你没有真正的死锁(但是死锁外观可能暗示的情况更少)。

第三,正如有人建议的那样,检查你是否有一些慢查询并调整它。

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

我没有看到您正在讨论的SQL Server版本,但每个版本的死锁管理都比前一版本更好,而SQL Server 2000在本主题中特别讨厌。

问候
马西莫

答案 3 :(得分:0)

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

尝试在探查器中运行跟踪运行(选择空白模板),选择“死锁图形事件”,然后在出现的新选项卡上(事件提取设置),保存每个(检查“单独保存死锁XML事件”) “)在自己的文件中。在xml查看器中打开此文件,可以很容易地判断发生了什么。包含每个进程,包含procedure / trigger / etc调用等的执行堆栈,所有锁也在那里。我发现很难相信col = @值的两个DELETE导致问题,查看执行堆栈,是否有触发器或其他事情发生?

查看文件的“资源列表”部分,它将显示导致死锁的每个进程锁定和保留的内容。弄清楚如何解锁其中一个并解决死锁问题。

答案 4 :(得分:0)

  

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

     

...是的,宣布了一项交易   确保整个对象图是   如果有的话,删除或保持原样   一个错误。没有其他数据库   一些交易中的操作   读取在事务之前完成   是开始但没有在里面...

我不确定我理解在交易之外升级读锁的原因,我认为这只会加剧问题。我的经验是,预先指定锁定提示会带来更多弊大于利。

有人说......

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

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

另外,请查看SQL语句的执行计划,了解锁升级是如何发生的。

答案 5 :(得分:0)

多年前,我在一张每天超过十亿笔交易的桌面上遇到了同样的问题。

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

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

答案 6 :(得分:0)

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

delete from part_data where part_id = @P0

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

所以你有两个选择

  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

  3. - 您可以删除那些要删除的记录而不锁定其他记录