Mar*_*man 17 sql-server deadlock primary-key sql-server-2012
我有一份死锁报告,告诉我有一个涉及到的冲突
waitresource="KEY: 9:72057632651542528 (543066506c7c)"
Run Code Online (Sandbox Code Playgroud)
我可以看到这个:
<keylock hobtid="72057632651542528" dbid="9" objectname="MyDatabase.MySchema.MyTable" indexname="MyPrimaryKeyIndex" id="locka8c6f4100" mode="X" associatedObjectId="72057632651542528">
Run Code Online (Sandbox Code Playgroud)
该范围内<resource-list>
的元素。
我希望能够找到键的实际值(例如,id = 12345)。我需要使用什么 SQL 语句来获取该信息?
dba*_*old 15
您拥有 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
Run Code Online (Sandbox Code Playgroud)
然后,您可以运行以下语句来识别表中的行(如果它仍然存在):-
SELECT %%LOCKRES%%, *
FROM [TABLE NAME] WITH(INDEX(MyPrimaryKeyIndex))
WHERE %%LOCKRES%% = '(543066506c7c)'
Run Code Online (Sandbox Code Playgroud)
但是请注意上面的语句,它会扫描目标表,因此在 READ UNCOMMITTED 中运行并监视您的服务器。
这是格兰特弗里奇关于 %%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/
Bat*_*ech 12
@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
;
Run Code Online (Sandbox Code Playgroud)
这是使用每个答案中的片段修改的示例脚本。
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;
Run Code Online (Sandbox Code Playgroud)
Kin*_*hah 10
这是对DBAFromTheCold和Aaron Bertrand已经发布的答案的补充。
微软仍然保留%%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
Run Code Online (Sandbox Code Playgroud)
另请参阅有关以下内容的优秀博客文章:可疑死锁和不那么逻辑锁的奇特案例
对不起,已经在研究这个答案,并在另一个出现时发布。添加为社区 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;
Run Code Online (Sandbox Code Playgroud)
当然,您可以在没有动态 SQL 的情况下执行此操作,但这为您提供了一个很好的代码片段或存储过程模板,您可以在其中插入值,如果这是您经常解决的问题。(您也可以参数化表名,也可以内置解析 KEY: 字符串来动态确定所有内容,但我认为这可能超出了本文的范围。)