我们遇到了在一个环境中进行转换死锁的问题(同样的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?
答案 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]表上持有一个对象级别的共享锁 - 由于两件事的组合,这很可能(没有代码时无法确定):
除非我们能够看到过程中的代码以及可能包含索引的表模式,否则这可能是我能给你的最佳猜测/信息。如果您可以发布过程代码,表模式和索引结构,那么应该能够轻松确定具体情况。
至于解释你的死锁输出,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的概述。
好的,我们需要从你的新问题中直接设置几件事情:
从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触发器)?另外,在执行相关存储过程之前,是否有任何代码在同一会话中运行?