存储过程导致的转换死锁

时间:2009-11-25 11:40:16

标签: sql-server stored-procedures locking deadlock

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

有问题的存储过程会在表(cmsreceipt)中插入一行,该表具有更新另一个表(cmsreceiptarchive)的触发器。为了尝试防止死锁,使用xlock对cmsreceiptarchive表进行选择,在插入之前完成rowlock以获取触发器更新的表上的锁。这适用于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?

1 个答案:

答案 0 :(得分:3)

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

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

至于你的情况下的死锁 - 你在你的问题中提到,为了避免死锁,你在插入[cmsreceipt]之前的过程中的[cmsreceiptarchive]表中使用xlock,rowlock显式选择了一个触发更新的触发器[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]表上持有一个对象级别的共享锁 - 由于两件事的组合,这很可能(没有代码时无法确定):

  1. 您正在使用可序列化的隔离模型,该模型将在事务持续期间保存共享锁(与事务中特定语句的持续时间相反,如在读取提交类型隔离模式中的情况)< / LI>
  2. 您正在过程/代码中执行某些操作以要求表级共享锁(可能是表扫描,或者大块扫描触及足够的行以升级到表级锁定而不是范围锁定) d通常在可序列化的事务中看到。)
  3. 除非我们能够看到过程中的代码以及可能包含索引的表模式,否则这可能是我能给你的最佳猜测/信息。如果您可以发布过程代码,表模式和索引结构,那么应该能够轻松确定具体情况。

    至于解释你的死锁输出,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的概述。


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

    好的,我们需要从你的新问题中直接设置几件事情:

    1. DBCC USEROPTIONS不是数据库级别的上下文,它是一个会话(即spid,连接)级别上下文 - 您从此返回的内容特定于其运行的会话(在这种情况下,您在SSMS中的连接或无论你运行它是什么.READ COMMITTED很可能是默认的隔离级别(除非已经改变),但是死锁场景中涉及的spid是使用SERIALIZABLE隔离模型(它显示在上面的输出中)。
    2. 您错误地解释了SQL_Menace在您发布的链接中所说的内容 - 他说过程中的代码将在序列化隔离模型中运行,以便在过程执行之外的会话中使用内联代码,因为它是在过程中显式设置为该级别,而不是因为默认情况下是这样。为了更好地解释,这里是他使用的确切示例以及一些其他评论来帮助理解
    3. 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表。

      您是否还可以发布触发器中包含的代码,以便我们可以看到您在触发器中正在做什么可能会影响事物(即它是一个替代触发器与一个for / after触发器)?另外,在执行相关存储过程之前,是否有任何代码在同一会话中运行?