sql-server - 存储过程导致的转换死锁

标签 sql-server stored-procedures locking deadlock

我们在一个环境中进行转换死锁时遇到了一个问题(相同的proc +触发器至少在其他四个环境中起作用)。

有问题的存储过程将一行插入到具有触发器的表(cmsreceipt)中,该触发器将更新另一个表(cmsreceiptarchive)。若要尝试防止死锁,请在具有xlock的cmsreceiptarchive表上进行选择,在插入之前先进行行锁,以获取触发器更新的表上的锁。这适用于db的四个版本,但不适用于这一环境(sql 2005)。

我将在下面复制死锁图,但是对我来说,似乎我们在表CmsReceipt上花费的表扫描时间很长,这使另一个运行相同proc的SPID也可以在表上获得共享锁,并且然后他们准备在CmsReceipt上进行更新后,都会尝试获取IX锁。

我检查了索引(一个聚集索引和两个非聚集索引),它们与其他工作正常的数据库匹配,所以我不知道为什么要在该数据库上进行表扫描,而在其他数据库上却不行。

我尝试了各种提示(在主过程和触发器中),但无济于事。

救命!在此先感谢您的帮助。

<deadlock-list>
 <deadlock victim="process76d5708">
  <process-list>
<process id="process76d5708" taskpriority="0" logused="0" waitresource="OBJECT: 7:1550628567:0 " waittime="4776" ownerId="34034594" transactionguid="0x4e9e61bf45eed2429a05ad44fa09ec50" transactionname="user_transaction" lasttranstarted="2009-11-24T15:51:12.280" XDES="0x1e0ca5970" lockMode="IX" schedulerid="8" kpid="14340" status="suspended" spid="57" sbid="2" ecid="0" priority="0" trancount="3" lastbatchstarted="2009-11-24T15:51:17.513" lastbatchcompleted="2009-11-24T15:49:54.807" clientapp=".Net SqlClient Data Provider" hostname="XXX" hostpid="4804" loginname="XXXX" isolationlevel="serializable (4)" xactid="34034594" currentdb="1" lockTimeout="4294967295" clientoption1="673185824" clientoption2="128056">
<executionStack>
<frame procname="XXX.dbo.Main_InsertCmsReceipt" line="43" stmtstart="2388" stmtend="3096" sqlhandle="0x03000700d7b7b271d2daf900cb9c00000100000000000000">
insert into CmsReceipt with (updlock)  (
 CmsReceiptId,
 ModifiedAt,
 ModifiedBy,
 CmsMessageId,
 Status,
 Details,
 ReceiptTimestamp,
 SenderName,
 SenderId
    )
    values (
 @New_CmsReceiptId,
 @New_ModifiedAt,
 @New_ModifiedBy,
 @New_CmsMessageId,
 @New_Status,
 @New_Details,
 @New_ReceiptTimestamp,
 @New_SenderName,
 @New_SenderId
)     </frame>
</executionStack>
<inputbuf>

Proc [Database Id = 7 Object Id = 1907537879]    </inputbuf>
   </process>
   <process id="process70a1dc8" taskpriority="0" logused="0" waitresource="OBJECT: 7:1550628567:0 " waittime="4498" ownerId="34034604" transactionguid="0x6719e8b21f633a48bf47c77a62f2af2c" transactionname="user_transaction" lasttranstarted="2009-11-24T15:51:12.483" XDES="0x1e1a77970" lockMode="IX" schedulerid="6" kpid="13632" status="suspended" spid="69" sbid="2" ecid="0" priority="0" trancount="3" lastbatchstarted="2009-11-24T15:51:17.780" lastbatchcompleted="2009-11-24T15:49:54.807" clientapp=".Net SqlClient Data Provider" hostname="XXXX" hostpid="4804" loginname="XXXXXX" isolationlevel="serializable (4)" xactid="34034604" currentdb="1" lockTimeout="4294967295" clientoption1="673185824" clientoption2="128056">
    <executionStack>
     <frame procname="XXX.dbo.Main_InsertCmsReceipt" line="43" stmtstart="2388" stmtend="3096" sqlhandle="0x03000700d7b7b271d2daf900cb9c00000100000000000000">
insert into CmsReceipt with (updlock)  (
CmsReceiptId,
 ModifiedAt,
 ModifiedBy,
 CmsMessageId,
 Status,
 Details,
 ReceiptTimestamp,
 SenderName,
 SenderId
    )
    values (
 @New_CmsReceiptId,
 @New_ModifiedAt,
 @New_ModifiedBy,
 @New_CmsMessageId,
 @New_Status,
 @New_Details,
 @New_ReceiptTimestamp,
 @New_SenderName,
 @New_SenderId
)     </frame>
    </executionStack>
    <inputbuf>
Proc [Database Id = 7 Object Id = 1907537879]    </inputbuf>
   </process>
  </process-list>
  <resource-list>
   <objectlock lockPartition="0" objid="1550628567" subresource="FULL" dbid="7" objectname="XXX.dbo.CmsReceipt" id="lock9c4eec80" mode="S" associatedObjectId="1550628567">
    <owner-list>
     <owner id="process70a1dc8" mode="S"/>
    </owner-list>
<waiter-list>
     <waiter id="process76d5708" mode="IX" requestType="convert"/>
    </waiter-list>
   </objectlock>
   <objectlock lockPartition="0" objid="1550628567" subresource="FULL" dbid="7" objectname="XXX.dbo.CmsReceipt" id="lock9c4eec80" mode="S" associatedObjectId="1550628567">
    <owner-list>
     <owner id="process76d5708" mode="S"/>
    </owner-list>
    <waiter-list>
     <waiter id="process70a1dc8" mode="IX" requestType="convert"/>
    </waiter-list>
   </objectlock>


PS是否有比每行开头4个空格更简单的方法来显示xml?

最佳答案

首先,如果您可以发布过程代码,表模式和索引结构,则对确定具体情况将非常有帮助。

接下来要注意的是,您正在使用serializable transactions,这是最严格的悲观锁定形式(会话的隔离级别在死锁图输出过程信息列表中可见)。可能是不需要的-如果您使用的是.NET TransactionScope库,则我相信它们默认情况下使用Serializable,并且您需要明确指定appropriate Isolation Level。如果由于某种原因确实需要可序列化事务的语义,请查看Snapshot Isolation instead,这是一种乐观的并发形式,它支持序列化语义。几乎可以肯定,这是您在此处陷入僵局的原因,我将在下面进一步解释。

至于您遇到的死锁,您在问题中提到要避免使用xlock显式选择死锁,请在插入[cmsreceipt]之前从过程[cmsreceiptarchive]表中选择行锁,这会触发更新[cmsreceiptarchive]的触发器]表(在这里我不会讨论这是否是正确的方法,因为我们看不到代码或场景,但这很可能是不必要的)。回到手头的问题-在这种情况下,您在[cmsreceiptarchive]表/索引上没有死锁,在插入点,您在[cmsreceipts]表本身上没有死锁,因此您针对[cmsreceiptarchive]执行选择实际上与该特定死锁无关。用更简单的方法解释死锁图:

SPID 57 is running (line 43 of procedure XXX.dbo.Main_InsertCmsReceipt):
    insert into CmsReceipt with (updlock) ( CmsReceiptId, ModifiedAt, ModifiedBy, CmsMessageId, Status, Details, ReceiptTimestamp, SenderName, SenderId ) 
    values ( @New_CmsReceiptId, @New_ModifiedAt, @New_ModifiedBy, @New_CmsMessageId, @New_Status, @New_Details, @New_ReceiptTimestamp, @New_SenderName, @New_SenderId )

    * HOLDS a Shared lock on dbo.CmsReceipt

    * WAITING for an IX lock (convert from the S lock) on dbo.CmsReceipt
        (SPID 69 holds a conflicting Shared Object)

SPID 69 is running (line 43 of procedure XXX.dbo.Main_InsertCmsReceipt):
    insert into CmsReceipt with (updlock) ( CmsReceiptId, ModifiedAt, ModifiedBy, CmsMessageId, Status, Details, ReceiptTimestamp, SenderName, SenderId ) 
    values ( @New_CmsReceiptId, @New_ModifiedAt, @New_ModifiedBy, @New_CmsMessageId, @New_Status, @New_Details, @New_ReceiptTimestamp, @New_SenderName, @New_SenderId )

    * HOLDS a Shared lock on dbo.CmsReceipt

    * WAITING for an IX lock (convert from the S lock) on dbo.CmsReceipt
        (SPID 57 holds a conflicting Shared Object)


如您所见,没有提到[cmsreceiptarchive]表。您有2个spid,每个spid都在[cmsreceipt]表上持有对象级的共享锁-由于两件事的结合,这很可能(不确定是否没有代码):


您正在使用可序列化的隔离模型,该模型将在事务期间保留共享锁(这与事务中特定语句的持续时间相反,在读提交类型隔离模式中会如此)
您正在过程/代码中更早地执行一些操作,以要求使用表级共享锁(例如表扫描或大块扫描,触摸足够多的行以升级为表级锁和范围锁,如通常所见)在可序列化的交易中)。


除非我们在过程中看到代码,在表模式中可能看到包括索引的代码,否则这可能是我能给您的最佳猜测/信息。如果您可以发布过程代码,表架构和索引结构,则应该能够轻松确定具体发生了什么。

至于解释死锁输出,Bart Duncan有一个3-part series on deciphering deadlock output,强烈推荐阅读(这是我在这里使用的,通常是我经常使用的),以帮助理解/理解正在发生的事情。您还可以查看concurrency, isolation models and the effect on standard DML operations along with demo scripts in here的概述。



编辑:添加对答案中提出的新问题的答复

好了,我们需要从您的新问题中直接弄清几件事:


DBCC USEROPTIONS不是数据库级别的上下文,而是会话(即spid,连接)级别的上下文-您从中看到的返回值是特定于运行该会话的(在这种情况下,您在SSMS中的连接或任何您正在使用的会话) READ COMMITTED很可能是默认的隔离级别(除非已更改),但是死锁场景中涉及的sps使用SERIALIZABLE隔离模型(如上面的输出所示)。
您误解了SQL_Menace在您发布的链接中所说的内容-他是说过程内部的代码将在过程执行之外的会话中以内联代码的可序列化隔离模型运行,因为该过程在过程执行之外的会话中被设置为该级别。该过程,不是因为默认情况下是这样。为了更好地解释,这是他使用的确切示例,并附带了一些其他注释,以帮助您理解


摘自here的代码示例,添加了一些注释:

use tempdb;
go

-- Create a test procedure to demonstrate with
create proc usp_test
as
-- Set the isolation level to read uncommitted - this will be the level used
-- for the duration of the procedure execution and any code within this procedure
-- unless explicitly set otherwise via another set statement or query hints
set transaction isolation level read uncommitted;

-- This will show you that the isolation level is 1, which is equivalent
-- to read uncommitted
select  transaction_isolation_level, session_id 
from    sys.dm_exec_sessions
where   session_id = @@spid;
go


-- Now, run some code (what SQL_Menace is referring to as *inline* code)


-- Check the current isolation level, should be the default, which is 
-- by default READ COMMITTED (equivalent to 2)
select  transaction_isolation_level, session_id 
from    sys.dm_exec_sessions
where   session_id = @@spid;

-- Explicitly set the isolation level to something else, serializable. This
-- will set the isolation method to serializable for this session and any
-- code executed in this context, unless explicitly set to something else
set transaction isolation level serializable;

-- Take another look at the isolation level - now will be 4, serializable
select  transaction_isolation_level, session_id 
from    sys.dm_exec_sessions
where   session_id = @@spid;

-- Execute the stored procedure - note that within the stored procedure's
-- context, the isolation level is running at 1 (read uncommitted)
exec usp_test;

-- Check the isolation level in this session/context again - note that it
-- is again running under the serializable isolation level, since the
-- read uncommitted level only applies for the duration of the procedure
-- code context
select  transaction_isolation_level, session_id 
from    sys.dm_exec_sessions
where   session_id = @@spid;

-- Repeat the same tests using a different isolation level - it isn't
-- always serializable, it is whatever the session is set to, which can
-- be the default or whatever you explicitly set it to
set transaction isolation level repeatable read;

-- Now it is 3 (repeatable read)...
select  transaction_isolation_level, session_id 
from    sys.dm_exec_sessions
where   session_id = @@spid;

-- Still going to be 1 within the procedure
exec usp_test;

-- Back to 3 again (repeatable read)
select  transaction_isolation_level, session_id 
from    sys.dm_exec_sessions
where   session_id = @@spid;

go


-- Cleanup
drop procedure usp_test;
go


好的,现在回到僵局。如上所述,您的死锁是在[CmsReceipt]表上发生的,而不是在[CmsMessageUnarchived]表上发生的,因此在插入[CmsReceipt]表之前要进行的虚拟选择与死锁无关(或大多数情况下可能不会)-死锁在CmsReceipt表上,而不在Unarchived表上。

您是否还可以发布触发器中包含的代码,以便我们可以看到您在触发器中正在执行的操作可能会影响事物(即,它是替代触发器还是前/后触发器)?另外,在执行所讨论的存储过程之前,是否在同一会话中运行任何代码?

关于sql-server - 存储过程导致的转换死锁,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1796410/

相关文章:

sql-server - 存储过程事务

php - 按日期排序的记录在 View 和存储过程中正确显示,但在 PHP 结果中显示乱序

android - 以编程方式锁定 Android 设备

database - hibernate 和并发

sql - 将sql server连接设置为只读?

.net - MDX 查询生成器实现

SQL Server批量插入

sql-server - 有什么办法可以避免子查询吗?

Mysql存储过程问题

c# - Azure 是否有开箱即用的 key 分布式锁定方式?