SQL Server 2012 中删除和更新语句之间的死锁
Deadlock between delete and update statement in SQL Server 2012
我在 2 个存储过程之间的数据库中遇到问题,一个尝试更新,另一个想要删除大量数据。
我的 Offer
table 包含 5000 万行(我知道这不是一个好的做法,我正在清除数据)。
table 包含一个增量 ID(不是 offer_id),它是带有聚簇索引的主键。
xml_deadlock_report :
<event name="xml_deadlock_report" package="sqlserver" timestamp="2018-01-02T00:56:16.360Z">
<data name="xml_report">
<type name="xml" package="package0" />
<value>
<deadlock>
<victim-list>
<victimProcess id="process3697498" />
</victim-list>
<process-list>
<process id="process3697498" taskpriority="0" logused="127362200" waitresource="PAGE: 9:1:99977592 " waittime="6212" ownerId="32514985656" transactionname="DELETE" lasttranstarted="2018-01-02T01:55:56.853" XDES="0x902e53ed28" lockMode="IX" schedulerid="5" kpid="10104" status="suspended" spid="155" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2018-01-02T00:00:02.427" lastbatchcompleted="2018-01-02T00:00:02.427" lastattention="1900-01-01T00:00:00.427" clientapp="SQLAgent - TSQL JobStep (Job 0xAF5CC7B9127279438B52607063734954 : Step 1)" hostname="HB01-BOSQL-CL02" hostpid="5008" loginname="RUEDUCOMMERCE\hicham.boutaleb" isolationlevel="read committed (2)" xactid="32514985656" currentdb="9" lockTimeout="4294967295" clientoption1="671088928" clientoption2="128056">
<executionStack>
<frame procname="EchangesDb.dbo.PurgeGM2" line="433" stmtstart="57518" stmtend="57750" sqlhandle="0x03000900c9c6d33469f20e0158a8000001000000000000000000000000000000000000000000000000000000">
DELETE
TOP (100000)
OFFER
WHERE
ID IN (SELECT ID FROM #OFFERSTODELETE)
OPTION(MAXDOP 1)
</frame>
<frame procname="adhoc" line="1" sqlhandle="0x010009008b23cd0690ffbc006500000000000000000000000000000000000000000000000000000000000000">
Exec PurgeGM2 </frame>
</executionStack>
<inputbuf>
Exec PurgeGM2 </inputbuf>
</process>
<process id="process5b230c8" taskpriority="0" logused="183626528" waitresource="PAGE: 9:1:99056248 " waittime="2010" ownerId="32514934129" transactionname="user_transaction" lasttranstarted="2018-01-02T01:55:46.243" XDES="0xd0e49f16c0" lockMode="U" schedulerid="15" kpid="10684" status="suspended" spid="128" sbid="0" ecid="22" priority="0" trancount="0" lastbatchstarted="2018-01-02T01:55:46.240" lastbatchcompleted="2018-01-02T01:55:46.240" lastattention="1900-01-01T00:00:00.240" clientapp=".Net SqlClient Data Provider" hostname="HB01-BIZTALK01" hostpid="2620" isolationlevel="read committed (2)" xactid="32514934129" currentdb="9" lockTimeout="4294967295" clientoption1="673317152" clientoption2="128056">
<executionStack>
<frame procname="EchangesDb.dbo.offer_insert_diff" line="183" stmtstart="8450" stmtend="8780" sqlhandle="0x0300090048642c329947700146a8000001000000000000000000000000000000000000000000000000000000">
UPDATE o
SET tc_process_status = 0
FROM [dbo].[offer] AS o WITH(NOLOCK)
INNER JOIN Temp_OffersToMove AS t WITH(NOLOCK) ON (o.offer_id = t.offer_id) </frame>
</executionStack>
<inputbuf>
Proc [Database Id = 9 Object Id = 841770056]
</inputbuf>
</process>
</process-list>
<resource-list>
<pagelock fileid="1" pageid="99977592" dbid="9" subresource="FULL" objectname="EchangesDb.dbo.offer" id="lock3d70957380" mode="U" associatedObjectId="72057595568062464">
<owner-list>
<owner id="process5b230c8" mode="U" />
</owner-list>
<waiter-list>
<waiter id="process3697498" mode="IX" requestType="wait" />
</waiter-list>
</pagelock>
<pagelock fileid="1" pageid="99056248" dbid="9" subresource="FULL" objectname="EchangesDb.dbo.offer" id="lock6d7712ab80" mode="IX" associatedObjectId="72057595568062464">
<owner-list>
<owner id="process3697498" mode="IX" />
</owner-list>
<waiter-list>
<waiter id="process5b230c8" mode="U" requestType="wait" />
</waiter-list>
</pagelock>
</resource-list>
</deadlock>
</value>
</data>
</event>
Offer.Offer_Id
不是主键并不重要。锁定封面行、页或 tables,而不是列。如果 table 中的一个字段发生更改,则整行至少被锁定。
根据它的写法,我假设你的删除程序是运行宁这个声明:
DELETE TOP (100000) OFFER
WHERE ID IN (
SELECT ID
FROM #OFFERSTODELETE
)
OPTION (MAXDOP 1)
在某种循环中,可能每次迭代都有提交(可能还有检查点),直到没有行被删除?而#OFFERSTODELETE
大概是几百万行?
您可以通过减少每次迭代删除的行数来降低死锁的可能性。比如说 100
或 500
。这将减少锁的数量,并降低将锁升级为页锁或 table 锁的可能性。当然,这样总体上会花费更长的时间,但锁的重量会减轻。如果您的用户都共享一个时区,或者(甚至更好)在计划的停机时间内,在一夜之间 运行 您的删除也是一个好主意。然后,您可能会删除 MAXDOP
限制,而不必担心 CPU 的使用。
除此之外,如果不查看每个过程的全部内容,就不可能提出任何建议。您可以重写这两个过程,使它们不会相互死锁。不幸的是,这并不总是可能的。
还有:
My Offer
table contains 50 million rows (I know not a good practice, I'm purging the data).
这个尺码 table 本身 没有任何问题。说实话,它甚至不是特别大。如果您有一个良好的集群索引并且始终适当地过滤您的数据,那么您应该不会因 table 大小而对性能产生巨大影响。只要您进行日常统计和索引维护,您可能根本没有任何问题。
感谢大家的帮助。
我的问题已通过 Erland's 的建议和一些额外的工作得到解决:
1) 减小批量大小(找到合适的三分之一,对我来说是 5000)。
2) 将清除的死锁优先级设置为低。
3) 为清除中的死锁添加重试(对于 1025 错误)。
4) 减少我的临时 table 的大小,其中包含每次迭代后要删除的行(按 ID 从订单中删除前 5000 行)
现在可以正常使用了。
非常感谢。
我在 2 个存储过程之间的数据库中遇到问题,一个尝试更新,另一个想要删除大量数据。
我的 Offer
table 包含 5000 万行(我知道这不是一个好的做法,我正在清除数据)。
table 包含一个增量 ID(不是 offer_id),它是带有聚簇索引的主键。
xml_deadlock_report :
<event name="xml_deadlock_report" package="sqlserver" timestamp="2018-01-02T00:56:16.360Z">
<data name="xml_report">
<type name="xml" package="package0" />
<value>
<deadlock>
<victim-list>
<victimProcess id="process3697498" />
</victim-list>
<process-list>
<process id="process3697498" taskpriority="0" logused="127362200" waitresource="PAGE: 9:1:99977592 " waittime="6212" ownerId="32514985656" transactionname="DELETE" lasttranstarted="2018-01-02T01:55:56.853" XDES="0x902e53ed28" lockMode="IX" schedulerid="5" kpid="10104" status="suspended" spid="155" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2018-01-02T00:00:02.427" lastbatchcompleted="2018-01-02T00:00:02.427" lastattention="1900-01-01T00:00:00.427" clientapp="SQLAgent - TSQL JobStep (Job 0xAF5CC7B9127279438B52607063734954 : Step 1)" hostname="HB01-BOSQL-CL02" hostpid="5008" loginname="RUEDUCOMMERCE\hicham.boutaleb" isolationlevel="read committed (2)" xactid="32514985656" currentdb="9" lockTimeout="4294967295" clientoption1="671088928" clientoption2="128056">
<executionStack>
<frame procname="EchangesDb.dbo.PurgeGM2" line="433" stmtstart="57518" stmtend="57750" sqlhandle="0x03000900c9c6d33469f20e0158a8000001000000000000000000000000000000000000000000000000000000">
DELETE
TOP (100000)
OFFER
WHERE
ID IN (SELECT ID FROM #OFFERSTODELETE)
OPTION(MAXDOP 1)
</frame>
<frame procname="adhoc" line="1" sqlhandle="0x010009008b23cd0690ffbc006500000000000000000000000000000000000000000000000000000000000000">
Exec PurgeGM2 </frame>
</executionStack>
<inputbuf>
Exec PurgeGM2 </inputbuf>
</process>
<process id="process5b230c8" taskpriority="0" logused="183626528" waitresource="PAGE: 9:1:99056248 " waittime="2010" ownerId="32514934129" transactionname="user_transaction" lasttranstarted="2018-01-02T01:55:46.243" XDES="0xd0e49f16c0" lockMode="U" schedulerid="15" kpid="10684" status="suspended" spid="128" sbid="0" ecid="22" priority="0" trancount="0" lastbatchstarted="2018-01-02T01:55:46.240" lastbatchcompleted="2018-01-02T01:55:46.240" lastattention="1900-01-01T00:00:00.240" clientapp=".Net SqlClient Data Provider" hostname="HB01-BIZTALK01" hostpid="2620" isolationlevel="read committed (2)" xactid="32514934129" currentdb="9" lockTimeout="4294967295" clientoption1="673317152" clientoption2="128056">
<executionStack>
<frame procname="EchangesDb.dbo.offer_insert_diff" line="183" stmtstart="8450" stmtend="8780" sqlhandle="0x0300090048642c329947700146a8000001000000000000000000000000000000000000000000000000000000">
UPDATE o
SET tc_process_status = 0
FROM [dbo].[offer] AS o WITH(NOLOCK)
INNER JOIN Temp_OffersToMove AS t WITH(NOLOCK) ON (o.offer_id = t.offer_id) </frame>
</executionStack>
<inputbuf>
Proc [Database Id = 9 Object Id = 841770056]
</inputbuf>
</process>
</process-list>
<resource-list>
<pagelock fileid="1" pageid="99977592" dbid="9" subresource="FULL" objectname="EchangesDb.dbo.offer" id="lock3d70957380" mode="U" associatedObjectId="72057595568062464">
<owner-list>
<owner id="process5b230c8" mode="U" />
</owner-list>
<waiter-list>
<waiter id="process3697498" mode="IX" requestType="wait" />
</waiter-list>
</pagelock>
<pagelock fileid="1" pageid="99056248" dbid="9" subresource="FULL" objectname="EchangesDb.dbo.offer" id="lock6d7712ab80" mode="IX" associatedObjectId="72057595568062464">
<owner-list>
<owner id="process3697498" mode="IX" />
</owner-list>
<waiter-list>
<waiter id="process5b230c8" mode="U" requestType="wait" />
</waiter-list>
</pagelock>
</resource-list>
</deadlock>
</value>
</data>
</event>
Offer.Offer_Id
不是主键并不重要。锁定封面行、页或 tables,而不是列。如果 table 中的一个字段发生更改,则整行至少被锁定。
根据它的写法,我假设你的删除程序是运行宁这个声明:
DELETE TOP (100000) OFFER
WHERE ID IN (
SELECT ID
FROM #OFFERSTODELETE
)
OPTION (MAXDOP 1)
在某种循环中,可能每次迭代都有提交(可能还有检查点),直到没有行被删除?而#OFFERSTODELETE
大概是几百万行?
您可以通过减少每次迭代删除的行数来降低死锁的可能性。比如说 100
或 500
。这将减少锁的数量,并降低将锁升级为页锁或 table 锁的可能性。当然,这样总体上会花费更长的时间,但锁的重量会减轻。如果您的用户都共享一个时区,或者(甚至更好)在计划的停机时间内,在一夜之间 运行 您的删除也是一个好主意。然后,您可能会删除 MAXDOP
限制,而不必担心 CPU 的使用。
除此之外,如果不查看每个过程的全部内容,就不可能提出任何建议。您可以重写这两个过程,使它们不会相互死锁。不幸的是,这并不总是可能的。
还有:
My
Offer
table contains 50 million rows (I know not a good practice, I'm purging the data).
这个尺码 table 本身 没有任何问题。说实话,它甚至不是特别大。如果您有一个良好的集群索引并且始终适当地过滤您的数据,那么您应该不会因 table 大小而对性能产生巨大影响。只要您进行日常统计和索引维护,您可能根本没有任何问题。
感谢大家的帮助。
我的问题已通过 Erland's 的建议和一些额外的工作得到解决:
1) 减小批量大小(找到合适的三分之一,对我来说是 5000)。
2) 将清除的死锁优先级设置为低。
3) 为清除中的死锁添加重试(对于 1025 错误)。
4) 减少我的临时 table 的大小,其中包含每次迭代后要删除的行(按 ID 从订单中删除前 5000 行)
现在可以正常使用了。
非常感谢。