Wie kann ich einen Schlüssel in einem SQL Server-Deadlock-Bericht in den Wert konvertieren?

15

Ich habe einen Deadlock-Bericht, aus dem hervorgeht, dass ein Konflikt mit waitresource = "KEY: 9: 72057632651542528 (543066506c7c)" aufgetreten ist.

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

innerhalb von <resource-list>. Ich möchte in der Lage sein, den tatsächlichen Wert für den Schlüssel zu finden (z. B. id = 12345). Welche SQL-Anweisung muss ich verwenden, um diese Informationen zu erhalten?

Mark Freeman
quelle

Antworten:

9

Die Antworten von @Kin, @AaronBertrand und @DBAFromTheCold sind großartig und sehr hilfreich. Eine wichtige Information, die ich während des Testens herausgefunden habe, dass die anderen Antworten ausgelassen wurden, ist, dass Sie den Index verwenden müssen, der von sys.partitionsdem angegebenen Index zurückgegeben wird , HOBT_IDwenn Sie nachschlagen %%lockres%%(über einen Index-Abfrage-Hinweis). Dieser Index ist nicht immer der PK- oder Clustered-Index.

Beispielsweise:

--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
;

Hier ist ein Beispielskript, das mit Stücken aus jeder dieser Antworten modifiziert wurde.

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;
BateTech
quelle
Das automatische Ermitteln des Datenbanknamens ist hier zusammen mit dem Indexhinweis eine nette Bereicherung. Vielen Dank!
Mark Freeman
14

Sie haben die hobt_id, sodass die folgende Abfrage die Tabelle identifiziert:

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

Anschließend können Sie die folgende Anweisung ausführen, um die Zeile in der Tabelle zu identifizieren (falls sie noch vorhanden ist):

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

Seien Sie vorsichtig mit der obigen Anweisung, sie scannt jedoch die Zieltabelle. Führen Sie also READ UNCOMMITTED aus und überwachen Sie Ihren Server.

Hier ist ein Artikel von Grant Fritchey über %% LOCKRES %% - http://www.scarydba.com/2010/03/18/undocumented-virtual-column-lockres/

Und hier ist ein Artikel aus meinem eigenen Blog über die Verwendung von %% LOCKRES %% zum Identifizieren von Zeilen aus einem erweiterten Ereignis: - https://dbafromthecold.wordpress.com/2015/02/24/identifying-blocking-via-extended-events/

dbafromthecold
quelle
Vielen Dank für die schnelle Antwort und für das Einbinden der Links zu den hilfreichen Blog-Beiträgen.
Mark Freeman
9

Dies ist eine Ergänzung zu den bereits von DBAFromTheCold und Aaron Bertrand geposteten Antworten .

Microsoft hat noch %%lockres%%als undokumentierte Funktion verlassen .

Unten finden Sie das Skript, das Ihnen helfen wird :

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

Lesen Sie auch diesen hervorragenden Blogeintrag über: Der seltsame Fall des zweifelhaften Deadlocks und die nicht so logische Sperre

Kin Shah
quelle
Ich nehme das als Antwort. Obwohl die von DBAFromTheCold und Aaron Bertrand bereitgestellten Lösungen beide funktionieren, kann ich auf diese Weise die Informationen erhalten, indem ich nur den KEY bereitstelle, wodurch dies für mich effizienter wird (obwohl bei einer zusätzlichen Belastung der Datenbank Informationen erhalten werden, die ich bereits habe, aber erhalten würde) lieber nicht zusammenfügen).
Mark Freeman
Kin, ich glaube, Sie haben einen langen Weg hinter sich und ich bin immer beeindruckter von Ihren Antworten. Sie sollten jedoch Ihre Quelle (n) angeben, wenn Sie Code vorschlagen, der von einer anderen Person geschrieben wurde (identischer Code hier , hier und hier ).
Aaron Bertrand
@ AaronBertrand Ich hatte diesen Code für eine lange Zeit und ich hatte keine Referenz, da ich es verwendet habe. Vielen Dank, dass Sie auf den Verweis hingewiesen haben (ich werde ihn auch in mein Repository aufnehmen). Danke auch für die netten Worte! Ich muss viel lernen und der Gemeinschaft etwas zurückgeben. Entschuldigung und ich wollte den Verweis wirklich nicht zitieren .
Kin Shah
6

Entschuldigung, habe bereits an dieser Antwort gearbeitet und wollte etwas posten, als die andere erschien. Hinzufügen als Community-Wiki nur, weil es ein etwas anderer Ansatz ist und ein bisschen andere Informationen hinzufügt.

Das 543066506c7cist im Wesentlichen ein Hash des Primärschlüssels, und Sie können diese Zeile (und möglicherweise alle Zeilen mit einer Hash-Kollision) mit dieser dynamischen SQL abrufen:

-- 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;

Sie können dies natürlich ohne dynamisches SQL tun, aber dies gibt Ihnen eine schöne Vorlage für ein Snippet oder eine gespeicherte Prozedur, in die Sie einfach die Werte einfügen können, wenn dies etwas ist, das Sie häufig beheben. (Sie können auch den Tabellennamen parametrisieren und das Parsen der Zeichenfolge KEY: einbauen, um alles für Sie dynamisch zu bestimmen, aber ich dachte, dass dies für diesen Beitrag möglicherweise nicht möglich ist.)

Aaron Bertrand
quelle