6

我有一个 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 输入缓冲区:语言事件:(@P0 nvarchar(4000))delete from part_data where part_id = @P0 请求者:ResType:LockOwner Stype:'OR'Xdes:0x04B511C8 模式: U SPID:60 BatchID:0 ECID:0 TaskProxy:(0x058BE378) Value:0x3d08500 Cost:(0/1296)

节点:2

KEY: 5:72057594042384384 (f903d6d6e0ac) CleanCnt:2 Mode:X Flags: 0x0 Grant List 0: Owner:0x03D088A0 Mode: X Flg:0x0 Ref:0 Life:02000000 SPID:60 ECID:0 XactLockInfo:0x04B511EC SPID:60 ECID: 0 语句类型:DELETE 行号:1 输入缓冲区:语言事件:(@P0 nvarchar(4000))delete from part_data where part_id = @P0 请求者:ResType:LockOwner Stype:'OR'Xdes:0x04834250 模式:U SPID: 62 BatchID:0 ECID:0 TaskProxy:(0x047BA378) 值:0x3d089e0 成本:(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) 锁,所以我想知道为什么我会看到跟踪中显示的情况。

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

谢谢,布拉德。

编辑按要求添加了死锁图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>
4

7 回答 7

4

欢迎来到可怕。

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

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

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

于 2009-06-11T12:48:41.457 回答
2

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

示例(伪步骤)

开始反式

  • 将数据(例如 INSERT、UPDATE)写入表 Foo
  • 使用不同的连接死锁从 Foo 读取一些数据(触发表扫描),因为读取永远不会完成,因此永远不会提交事务
于 2009-05-08T13:37:10.533 回答
1

首先不要使用提示,通常 SQL Server 最好自己留下。

其次,验证您没有真正的死锁(但死锁外观可以暗示这种情况发生的频率要低得多)。

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

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

我看不出您在谈论哪个版本的 SQL Server,但每个后续版本都比前一个版本具有更好的死锁管理,并且 SQL Server 2000 在这个主题中特别讨厌。

问候
马西莫

于 2009-11-05T23:44:12.570 回答
0

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

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

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

于 2009-05-08T13:07:00.560 回答
0

...我决定使用 UPDLOCK 提示对有问题的表进行所有读取以“进行更新”,这样就可以避免死锁...

...是的,声明了一个事务,以确保在出现错误时删除整个对象图或保持原样。事务中没有其他数据库操作,在事务启动之前完成了一些读取,但其中没有......

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

话虽如此...

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

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

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

于 2009-07-20T18:36:01.037 回答
0

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

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

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

于 2013-12-27T14:54:09.637 回答
0

First of all it is two delete statements. These two SPIDs are executing the same statement

delete from part_data where part_id = @P0

and probably they wont be trying to delete the same record. Here deadlock happens because SQLServer cannot identify the row/rows from the table part_data using column part_id and it is locking other records also.

So you have two options

  1. Make part_id its primary key (if possible)

    or

  2. Alter the statement as follows. (Assuming that there is a primary key)

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

    -- you will get the primary key of those records that are to be deleted

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

-- You can delete those record which are to be deleted without locking other records

于 2016-11-30T09:24:37.130 回答