如何将SQL Server死锁报告中的键转换为值?


15

我有一个死锁报告,告诉我发生了涉及waitresource =“ KEY:9:72057632651542528(543066506c7c)”的冲突,并且我可以看到:

<keylock hobtid="72057632651542528" dbid="9" objectname="MyDatabase.MySchema.MyTable" indexname="MyPrimaryKeyIndex" id="locka8c6f4100" mode="X" associatedObjectId="72057632651542528">

在<resource-list>中。我希望能够找到密钥的实际值(例如,id = 12345)。我需要使用什么SQL语句来获取该信息?

Answers:


9

@ Kin,@ AaronBertrand和@DBAFromTheCold的答案很棒,而且非常有帮助。我在测试过程中发现的一条重要信息中,还有其他答案被遗漏了:在查找时(通过索引查询提示),您需要使用从sys.partitions给定返回的索引。该索引并不总是PK索引或聚集索引。HOBT_ID%%lockres%%

例如:

--Sometimes this does not return the correct results.
SELECT lockResKey = %%lockres%% ,* 
FROM [MyDB].[dbo].[myTable]  
WHERE %%lockres%% = @lockres
;
--But if you add the index query hint, it does return the correct results
SELECT lockResKey = %%lockres%% ,* 
FROM [MyDB].[dbo].[myTable] WITH(NOLOCK INDEX([IX_MyTable_NonClustered_index]))  
WHERE %%lockres%% = @lockres
;

这是一个示例脚本,使用每个答案中的片段进行了修改。

declare @keyValue varchar(256);
SET @keyValue = 'KEY: 5:72057598157127680 (92d211c2a131)' --Output from deadlock graph: process-list/process[waitresource] -- CHANGE HERE !
------------------------------------------------------------------------
--Should not have to change anything below this line: 
declare @lockres nvarchar(255), @hobbitID bigint, @dbid int, @databaseName sysname;
--.............................................
--PARSE @keyValue parts:
SELECT @dbid = LTRIM(SUBSTRING(@keyValue, CHARINDEX(':', @keyValue) + 1, CHARINDEX(':', @keyValue, CHARINDEX(':', @keyValue) + 1) - (CHARINDEX(':', @keyValue) + 1) ));
SELECT @hobbitID = convert(bigint, RTRIM(SUBSTRING(@keyValue, CHARINDEX(':', @keyValue, CHARINDEX(':', @keyValue) + 1) + 1, CHARINDEX('(', @keyValue) - CHARINDEX(':', @keyValue, CHARINDEX(':', @keyValue) + 1) - 1)));
SELECT @lockRes = RTRIM(SUBSTRING(@keyValue, CHARINDEX('(', @keyValue) + 0, CHARINDEX(')', @keyValue) - CHARINDEX('(', @keyValue) + 1));
--.............................................
--Validate DB name prior to running dynamic SQL
SELECT @databaseName = db_name(@dbid);  
IF not exists(select * from sys.databases d where d.name = @databaseName)
BEGIN
    RAISERROR(N'Database %s was not found.', 16, 1, @databaseName);
    RETURN;
END

declare @objectName sysname, @indexName sysname, @schemaName sysname;
declare @ObjectLookupSQL as nvarchar(max) = '
SELECT @objectName = o.name, @indexName = i.name, @schemaName = OBJECT_SCHEMA_NAME(p.object_id, @dbid)
FROM ' + quotename(@databaseName) + '.sys.partitions p
JOIN ' + quotename(@databaseName) + '.sys.indexes i ON p.index_id = i.index_id AND p.[object_id] = i.[object_id]
JOIN ' + quotename(@databaseName)+ '.sys.objects o on o.object_id = i.object_id
WHERE hobt_id = @hobbitID'
;
--print @ObjectLookupSQL
--Get object and index names
exec sp_executesql @ObjectLookupSQL
    ,N'@dbid int, @hobbitID bigint, @objectName sysname OUTPUT, @indexName sysname OUTPUT, @schemaName sysname OUTPUT'
    ,@dbid = @dbid
    ,@hobbitID = @hobbitID
    ,@objectName = @objectName output
    ,@indexName = @indexName output
    ,@schemaName = @schemaName output
;

DECLARE @fullObjectName nvarchar(512) = quotename(@databaseName) + '.' + quotename(@schemaName) + '.' + quotename(@objectName);
SELECT fullObjectName = @fullObjectName, lockIndex = @indexName, lockRes_key = @lockres, hobt_id = @hobbitID, waitresource_keyValue = @keyValue;

--Validate object name prior to running dynamic SQL
IF OBJECT_iD( @fullObjectName) IS NULL 
BEGIN
    RAISERROR(N'The object "%s" was not found.',16,1,@fullObjectName);
    RETURN;
END

--Get the row that was blocked
--NOTE: we use the NOLOCK hint to avoid locking the table when searching by %%lockres%%, which might generate table scans.
DECLARE @finalResult nvarchar(max) = N'SELECT lockResKey = %%lockres%% ,* 
FROM ' + @fullObjectName
+ ISNULL(' WITH(NOLOCK INDEX(' + QUOTENAME(@indexName) + ')) ', '')  
+ ' WHERE %%lockres%% = @lockres'
;

--print @finalresult
EXEC sp_executesql @finalResult, N'@lockres nvarchar(255)', @lockres = @lockres;

在这里,自动确定数据库名称以及索引提示是一个不错的附加值。谢谢!
Mark Freeman

14

您拥有hobt_id,因此以下查询将识别该表:-

SELECT o.name
FROM sys.partitions p
INNER JOIN sys.objects o ON p.object_id = o.object_id
WHERE p.hobt_id = 72057632651542528

然后,您可以运行以下语句来标识表中的行(如果仍然存在):

SELECT %%LOCKRES%%,  *
FROM [TABLE NAME] WITH(INDEX(MyPrimaryKeyIndex))
WHERE %%LOCKRES%% = '(543066506c7c)'

但是,请谨慎使用上述语句,它会扫描目标表,以便在READ UNCOMMITTED中运行并监视您的服务器。

这是Grant Fritchey撰写的有关%% LOCKRES %%的文章-http: //www.scarydba.com/2010/03/18/undocumented-virtual-column-lockres/

下面是我自己的博客关于使用%% LOCKRES %%从扩展的事件标识行的文章: - https://dbafromthecold.wordpress.com/2015/02/24/identifying-blocking-via-extended-events/


感谢您的快速回复,并包括指向有用的博客文章的链接。
Mark Freeman

9

这是对DBAFromTheColdAaron Bertrand已经发布的答案的补充。

Microsoft仍然保留%%lockres%%了未记录的功能

以下是可以帮助您的脚本

declare @databaseName varchar(100) = 'yourdatabaseName' --CHANGE HERE !
declare @keyValue varchar(100) = 'KEY: 9:72057632651542528 (543066506c7c)' --Output from deadlock graph -- CHANGE HERE !
declare @lockres varchar(100)
declare @hobbitID bigint

select @hobbitID = convert(bigint, RTRIM(SUBSTRING(@keyValue, CHARINDEX(':', @keyValue, CHARINDEX(':', @keyValue) + 1) + 1, CHARINDEX('(', @keyValue) - CHARINDEX(':', @keyValue, CHARINDEX(':', @keyValue) + 1) - 1)))

select @lockRes = RTRIM(SUBSTRING(@keyValue, CHARINDEX('(', @keyValue) + 1, CHARINDEX(')', @keyValue) - CHARINDEX('(', @keyValue) - 1))

declare @objectName sysname
declare @ObjectLookupSQL as nvarchar(max) = '
SELECT @objectName = o.name
FROM ' + quotename(@databaseName) + '.sys.partitions p
JOIN ' + quotename(@databaseName) + '.sys.indexes i ON p.index_id = i.index_id AND p.[object_id] = i.[object_id]
join ' + quotename(@databaseName)+ '.sys.objects o on o.object_id = i.object_id
WHERE hobt_id = ' + convert(nvarchar(50), @hobbitID) + ''

--print @ObjectLookupSQL
exec sp_executesql @ObjectLookupSQL
    ,N'@objectName sysname OUTPUT'
    ,@objectName = @objectName output

--print @objectName

declare @finalResult nvarchar(max) = N'select %%lockres%% ,* 
from ' + quotename(@databaseName) + '.dbo.' + @objectName + '
where %%lockres%% = ''(' + @lockRes + ')''
'
--print @finalresult
exec sp_executesql @finalResult

另请参阅以下出色的博客文章:可疑死锁和逻辑锁的奇怪案例


我选择这个作为答案。尽管DBAFromTheCold和Aaron Bertrand提供的解决方案都可以使用,但这使我仅提供KEY就可以获取信息,这对我来说更加有效(尽管在数据库上有一些额外的负载来获取我已经拥有的信息,但是而不是拼凑提供)。
Mark Freeman

Kin,我想您在这里已经走了很长一段路,而且您的回答一直给我留下越来越深刻的印象。但是,当您建议其他人编写的代码时(在此处此处此处相同的代码),您应该公开您的源代码。
亚伦·伯特兰

@AaronBertrand我已经有很长时间了,并且由于我一直在使用它,所以我没有任何参考。谢谢,因为您指出了参考(我也将其添加到存储库中)。另外,感谢您的客气话!我必须走很长一段路学习并回馈社区。抱歉,我的确表示不引用参考文献
金莎(Kin Shah)

6

抱歉,已经在解决此问题,并在其他人出现时发布。添加为社区Wiki的唯一原因是,这是一种稍微不同的方法,并添加了一些其他信息。

543066506c7c本质上是主键的哈希值,你可以用这种动态SQL检索行(并可能与哈希冲突的任何行):

-- Given: KEY: 9:72057632651542528 (543066506c7c)
-- and object = MyDatabase.MySchema.MyTable

DECLARE 
  @hobt BIGINT = 72057632651542528,
  @db SYSNAME = DB_NAME(9),
  @res VARCHAR(255) = '(543066506c7c)';

DECLARE @exec NVARCHAR(MAX) = QUOTENAME(@db) + N'.sys.sp_executesql';

DECLARE @sql NVARCHAR(MAX) = N'SELECT %%LOCKRES%%,*
  FROM MySchema.MyTable WHERE %%LOCKRES%% = @res;';

EXEC @exec @sql, N'@res VARCHAR(255)', @res;

当然,您可以在不使用动态SQL的情况下执行此操作,但是如果您要进行很多故障排除,那么这将为您提供一个不错的代码段或存储过程模板,您可以在其中插入值。(您还可以参数化表名,还可以建立KEY:字符串的解析来动态地确定所有内容,但我认为这可能超出了本文的范围。)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.