Come posso convertire una chiave in un report deadlock di SQL Server nel valore?


15

Ho un rapporto di deadlock che mi dice che c'era un conflitto che riguardava waitresource = "KEY: 9: 72057632651542528 (543066506c7c)" e posso vedere questo:

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

all'interno di <resource-list>. Voglio essere in grado di trovare il valore effettivo per la chiave (id = 12345, ad esempio). Quale istruzione SQL dovrei usare per ottenere tali informazioni?

Risposte:


9

Le risposte di @Kin, @AaronBertrand e @DBAFromTheCold sono fantastiche e sono state molto utili. Un'importante informazione che ho scoperto durante il test che le altre risposte lasciate è che è necessario utilizzare l'indice da cui viene restituito sys.partitionsil dato HOBT_IDquando si cerca %%lockres%%(tramite un suggerimento per la query dell'indice). Questo indice non è sempre l'indice PK o cluster.

Per esempio:

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

Ecco uno script di esempio modificato usando pezzi di ciascuna di queste risposte.

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;

Determinare automaticamente il nome del database è un buon valore aggiunto qui, insieme al suggerimento sull'indice. Grazie!
Mark Freeman,

14

Hai hobt_id in modo che la seguente query identificherà la tabella: -

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

Da questo è quindi possibile eseguire la seguente istruzione per identificare la riga nella tabella (se esiste ancora): -

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

Prestare attenzione con l'istruzione precedente, tuttavia eseguirà la scansione della tabella di destinazione, quindi esegui READ UNCOMMITTED e monitorerà il tuo server.

Ecco un articolo di Grant Fritchey su %% LOCKRES %% - http://www.scarydba.com/2010/03/18/undocumented-virtual-column-lockres/

Ed ecco un articolo dal mio blog sull'utilizzo di %% LOCKRES %% per identificare le righe di un evento esteso: - https://dbafromthecold.wordpress.com/2015/02/24/identifying-blocking-via-extended-events/


Grazie per la rapida risposta e per l'inclusione dei collegamenti agli utili post del blog.
Mark Freeman,

9

Questo è un supplemento alle risposte già pubblicate da DBAFromTheCold e Aaron Bertrand .

Microsoft ha ancora lasciato %%lockres%%come funzionalità non documentata .

Di seguito è riportato lo script che ti aiuterà :

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

Fai anche riferimento a questo eccellente post di blog su: Il curioso caso del vicolo cieco e il blocco non così logico


Lo sto scegliendo come risposta. Sebbene le soluzioni fornite da DBAFromTheCold e Aaron Bertrand funzionino entrambe, ciò mi consente di ottenere le informazioni fornendo solo KEY, rendendole più efficienti per me (anche se con un carico aggiuntivo sul database per ottenere informazioni che già ho, ma piuttosto non mettere insieme per fornire).
Mark Freeman,

Kin, penso che tu abbia fatto molta strada qui e sono sempre più colpito dalle tue risposte in ogni momento. Tuttavia, dovresti rivelare le tue fonti quando proponi un codice scritto da qualcun altro (codice identico qui , qui e qui ).
Aaron Bertrand

@AaronBertrand Ho avuto questo codice per molto tempo e non ho avuto alcun riferimento, dal momento che lo sto usando. Grazie poiché hai indicato il riferimento (lo aggiungerò anche nel mio repository). Inoltre, grazie per le belle parole! Devo fare molto per imparare e restituire alla comunità. Mi scuso e sinceramente non intendevo non citare il riferimento .
Kin Shah,

6

Siamo spiacenti, stavo già lavorando su questa risposta e stava per pubblicare quando è apparso l'altro. Aggiungendo come wiki della comunità solo perché è un approccio leggermente diverso e aggiunge un po 'di altre informazioni.

Il 543066506c7cè essenzialmente un hash della chiave primaria, e si possono recuperare quella riga (e potenzialmente tutte le righe con una collisione hash) utilizzando questo SQL dinamico:

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

Puoi farlo senza SQL dinamico, ovviamente, ma questo ti dà un bel modello per uno snippet o una procedura memorizzata in cui puoi semplicemente inserire i valori, se questo è qualcosa che risolvi molto. (Potresti anche parametrizzare il nome della tabella e potresti anche costruire l'analisi di KEY: string per determinare tutto per te in modo dinamico, ma ho pensato che potesse essere fuori portata per questo post.)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.