如何分析SQL Server中的deadlock trace

关于如何得到deadlock trace,参考:

http://blog.csdn.net/onlyqi/article/details/23172411


首先我们来看一个简单的例子,大结构非常简单:

1,process-list显示了两个进程之间发生了死锁process60fb88和processd11902c8。

2,vistim-list显示了process60fb88被选为了牺牲者。

2,后面的resource-list显示了两个进程争取并导致死锁的资源。

<deadlock>
	<victim-list>
		<victimProcess id="process60fb88" />
	</victim-list>
	<process-list>
		<process id="process60fb88" taskpriority="0" logused="0" waitresource="KEY: 9:72057597664231424 (7506ff9b7b0d)" waittime="4376" ownerId="2656658629" transactionname="SELECT" lasttranstarted="2014-04-09T23:01:35.743" XDES="0x80059940" lockMode="S" schedulerid="4" kpid="10640" status="suspended" spid="80" sbid="0" ecid="0" priority="0" trancount="0" lastbatchstarted="2014-04-09T23:01:35.657" lastbatchcompleted="2014-04-09T23:01:35.657" clientapp=".Net SqlClient Data Provider" hostname="BODCPRODVSQL128" hostpid="10088" loginname="PROD\s-propdata" isolationlevel="read committed (2)" xactid="2656658629" currentdb="9" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
			<executionStack>
				<frame procname="" line="9" stmtstart="336" stmtend="874" sqlhandle="0x030009003d00da3fa6087c0182a200000100000000000000" />
				<frame procname="" line="20" stmtstart="1022" stmtend="1206" sqlhandle="0x03000900941f284ed5929e00aba200000100000000000000" />
				<frame procname="" line="9" stmtstart="464" stmtend="642" sqlhandle="0x03000a006502e0715df5af00aba200000100000000000000" />
				<frame procname="" line="4" stmtstart="224" stmtend="420" sqlhandle="0x01000a00b6fca934509742900b0000000000000000000000" />
			</executionStack>
			<inputbuf>

                                    DECLARE @logText NVARCHAR(MAX)

                                    EXEC IntegratedService_ProcessLatestCommand @logText OUTPUT

                                    SELECT @logText   </inputbuf>
		</process>
		<process id="processd11902c8" taskpriority="0" logused="232" waitresource="KEY: 9:72057596808265728 (ed2e944beff9)" waittime="4379" ownerId="2656658630" transactionname="UPDATE" lasttranstarted="2014-04-09T23:01:35.743" XDES="0x80048570" lockMode="X" schedulerid="8" kpid="6620" status="suspended" spid="53" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2014-04-09T23:01:34.650" lastbatchcompleted="2014-04-09T23:01:34.650" clientapp=".Net SqlClient Data Provider" hostname="BODCPRODVSQL128" hostpid="10088" loginname="PROD\s-propdata" isolationlevel="read committed (2)" xactid="2656658630" currentdb="9" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
			<executionStack>
				<frame procname="" line="22" stmtstart="1230" stmtend="1496" sqlhandle="0x030009003d00da3fa6087c0182a200000100000000000000" />
				<frame procname="" line="20" stmtstart="1022" stmtend="1206" sqlhandle="0x03000900941f284ed5929e00aba200000100000000000000" />
				<frame procname="" line="9" stmtstart="464" stmtend="642" sqlhandle="0x03000a006502e0715df5af00aba200000100000000000000" />
				<frame procname="" line="4" stmtstart="224" stmtend="420" sqlhandle="0x01000a00b6fca934509742900b0000000000000000000000" />
			</executionStack>
			<inputbuf>

                                    DECLARE @logText NVARCHAR(MAX)

                                    EXEC IntegratedService_ProcessLatestCommand @logText OUTPUT

                                   SELECT @logText   </inputbuf>
		</process>
	</process-list>
	<resource-list>
		<keylock hobtid="72057597664231424" dbid="9" objectname="" indexname="" id="lockc99859500" mode="X" associatedObjectId="72057597664231424">
			<owner-list>
				<owner id="processd11902c8" mode="X" />
			</owner-list>
			<waiter-list>
				<waiter id="process60fb88" mode="S" requestType="wait" />
			</waiter-list>
		</keylock>
		<keylock hobtid="72057596808265728" dbid="9" objectname="" indexname="" id="lock2f4de2d00" mode="S" associatedObjectId="72057596808265728">
			<owner-list>
				<owner id="process60fb88" mode="S" />
			</owner-list>
			<waiter-list>
				<waiter id="processd11902c8" mode="X" requestType="wait" />
			</waiter-list>
		</keylock>
	</resource-list>
</deadlock>

下面是详细分析。

1,victim-list没什么可分析的。

2,process-list中关于各个process的详细信息很重要。

waitresource="KEY: 9:72057597664231424 (7506ff9b7b0d)"

当前process正在等待的资源。通常我们在resource-list中可以看到同样的信息。使用下面的sql查询等待的资源是什么:

下面使用的hobtid是heap or b-tree id的缩写。详细见sys.partotions的解释。

SELECT o.name, i.name 
FROM sys.partitions p 
JOIN sys.objects o ON p.object_id = o.object_id 
JOIN sys.indexes i ON p.object_id = i.object_id 
AND p.index_id = i.index_id 
WHERE p.hobt_id = 72057597664231

name name

--------------------------------------------------------------------

MatchService PK_Matcher_ID
从结果我们就可以知道,等待的资源是一个表MatchService的主键PK_Matcher_ID。考察另外一个process的waitresource我们可以得知等待的资源是同一个表的另外一个索引。至此我们找到了直接导致死锁的资源是什么。

同时可以看到两个process一个是x lock,一个是s lock。因此可以判定发生在该表上的一个修改语句和一个查询语句之间发生了死锁。

另外,上例中可以清晰的看到是keylock导致的死锁,因此查询partitions可以找到对应的object (sys.partitions contains a row for each partition of all the tables and most types of indexes in the database.)。但有时是其他类型的资源发生了死锁,例如pagelock, waitresource="PAGE: 9:1:28440841" 。 9是dbid; 1是fileid; 28440841是pageid。对于这种情况,使用下面的语句查询对应的资源:

DBCC TRACEON(3604)
GO
DBCC PAGE (9, 1, 28440841)
GO
DBCC TRACEOFF(3604)
GO

从返回的Metadata: objectId找到对应的objectid。


3,再看process中的inputbuf。这个tag表明了process正在运行的语句,因此对于定位死锁非常重要。但这里有一个问题,比如上例中,inputbuf是一个存储过程,其中又嵌套了很多其他的存储过程,但inputbuf是用户直接发出的sql,而我们需要在其中找出直接导致死锁的语句并优化,从而解决或减少死锁。自此我们已经有的信息是:导致死锁的语句由inputbuf中的语句调用,同时导致死锁的语句必定是对表MatchService的修改语句。如果存储过程很简单,到此DBA已经能够找到直接导致死锁的sql了,分析过程到此结束。而如果存储过程很复杂,则需要进一步分析。


4,现在再进一步考察tag, executionStack。executionStack表明了死锁发生时,由inputbuf调用的一系列sql。上例中有4条sql。同时仔细观察上例可以发生,两个process的executionStack是完全相同的,因此考察一个就可以了。另外,如果procname不为空则直接得到了sql,但上例中该tag为空。

自此我们希望把executionStack中的所有sql显示出来。使用下面的sql找出sqlhandle对应的在内存中的sql。需要注意的是,如果deadlock已经过去了一段时间,sqlhandle可能已经被从内存中清除掉了,这时就不可查了。还有sqlhandle是varbinaryd,所以查询时不可加引号。

另外还有一个有趣的地方:和其他程序语言报错时一样,stack最上的一条是最直接的错误,后面的错误都是该错误的上一层错误(这么解释可能有点乱,写过代码的同学能理解哈)。因此在上面说的存储过程调用存储过程的情况中,executionStack中第一条是直接导致死锁的sql,第二条是调用该sql的sql,以此类推,最后一条理论上就是inputbuf中的sql。

SELECT sql_handle AS Handle,
SUBSTRING(st.text, (qs.statement_start_offset/2)+1, 
((CASE qs.statement_end_offset
WHEN -1 THEN DATALENGTH(st.text)
ELSE qs.statement_end_offset
END - qs.statement_start_offset)/2) + 1) AS Text
FROM sys.dm_exec_query_stats AS qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st
where sql_handle = 0x030009003d00da3fa6087c0182a200000100000000000000
order by sql_handle

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

0x030009003D00DA3FA6087C0182A200000100000000000000SELECT TOP 1 @matcherQueueID = lhs.MatcherService_MatcherQueue_ID,    @rootOperationUID = Root_Operation_UID   FROM MatcherService_MatcherQueue lhs   WHERE lhs.Processing_State = 'MATCHING' OR    lhs.Processing_State = 'MATCHED'   ORDER BY Last_Execution_Date ASC       
0x030009003D00DA3FA6087C0182A200000100000000000000 SELECT Top 1 @ticketID = OperationLog_ID   FROM GEDemo.dbo.OperationLog    WHERE @rootOperationUID = Root_Operation_UID AND      Status = 0   ORDER BY OperationLog_ID ASC       
0x030009003D00DA3FA6087C0182A200000100000000000000 UPDATE MatcherService_MatcherQueue    SET Last_Execution_Date = GETDATE()   WHERE MatcherService_MatcherQueue_ID = @matcherQueueID 

注意看起来一个sql_handle有三条语句,原因是这三条sql是属于同一个存储过程的。

如果一个sql_handle包含的语句很多,比如是一个很长的存储过程,那么我们还可以使用一个有力的信息:executionStack中的line tag.这条语句表明了到底是哪一个sql直接导致了死锁。如果一条statement中又包含了很多表,那么还需要和死锁的资源结合起来判断是哪个表或索引的数据发生了死锁。


5,我们可能还需要找出包含该sql的具体存储过程,然后进行优化。出了用sql查询外,推荐使用一个叫“SQL Search”的第三方工具,很方便,免费的:

http://www.red-gate.com/products/sql-development/sql-search/?utm_source=google&utm_medium=cpc&utm_content=brand_aware&utm_campaign=sqlsearch&gclid=COLe-MfA1b0CFRFp7Aod5jwAqw





你可能感兴趣的:(如何分析SQL Server中的deadlock trace)