Como posso converter uma chave em um relatório de deadlock do SQL Server para o valor?

15

Eu tenho um relatório de deadlock que me diz que houve um conflito envolvendo waitresource = "KEY: 9: 72057632651542528 (543066506c7c)" e posso ver isso:

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

dentro de <lista de recursos>. Quero poder encontrar o valor real da chave (id = 12345, por exemplo). Qual instrução SQL eu precisaria usar para obter essas informações?

Mark Freeman
fonte

Respostas:

9

As respostas de @Kin, @AaronBertrand e @DBAFromTheCold são ótimas e foram muito úteis. Uma informação importante que encontrei durante o teste e que as outras respostas foram deixadas de fora é que você precisa usar o índice retornado de acordo sys.partitionscom o especificado HOBT_IDao pesquisar %%lockres%%(através de uma dica de consulta de índice). Esse índice nem sempre é o índice PK ou em cluster.

Por exemplo:

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

Aqui está um exemplo de script modificado usando partes de cada uma dessas respostas.

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
fonte
Determinar automaticamente o nome do banco de dados é um bom valor agregado aqui, junto com a dica do índice. Obrigado!
Mark Freeman
14

Você tem o hobt_id para que a seguinte consulta identifique a tabela: -

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

A partir disso, você pode executar a seguinte instrução para identificar a linha na tabela (se ela ainda existir): -

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

Tenha cuidado com a instrução acima, no entanto, ela examinará a tabela de destino, portanto execute READ UNCOMMITTED e monitore seu servidor.

Aqui está um artigo de Grant Fritchey sobre %% LOCKRES %% - http://www.scarydba.com/2010/03/18/undocumented-virtual-column-lockres/

E aqui está um artigo do meu próprio blog sobre o uso de %% LOCKRES %% para identificar linhas de um evento estendido: - https://dbafromthecold.wordpress.com/2015/02/24/identifying-blocking-via-extended-events/

dbafromthecold
fonte
Obrigado pela resposta rápida e por incluir os links para as postagens úteis do blog.
Mark Freeman
9

Este é um complemento para as respostas já publicadas por DBAFromTheCold e Aaron Bertrand .

A Microsoft ainda saiu %%lockres%%como recurso não documentado .

Abaixo está o script que irá ajudá-lo :

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

Consulte também este excelente post no blog: O caso curioso do impasse duvidoso e o bloqueio não tão lógico

Kin Shah
fonte
Eu estou escolhendo isso como a resposta. Embora as soluções fornecidas pelo DBAFromTheCold e Aaron Bertrand funcionem, isso me permite obter as informações fornecendo apenas o KEY, tornando-o mais eficiente para mim (embora com uma carga adicional no banco de dados para obter as informações que eu já tenho, mas seria em vez de não juntar para fornecer).
Mark Freeman
Kin, acho que você percorreu um longo caminho até aqui, e fico cada vez mais impressionado com suas respostas o tempo todo. No entanto, você deve revelar suas fontes ao propor código escrito por outra pessoa (código idêntico aqui , aqui e aqui ).
Aaron Bertrand
@AaronBertrand Eu tenho esse código há muito tempo e não tenho nenhuma referência, pois o uso. Obrigado desde que você apontou a referência (eu a adicionarei no meu repositório também). Além disso, obrigado pelas amáveis ​​palavras! Eu tenho que percorrer um longo caminho aprendendo e retribuindo à comunidade. Desculpas e eu sinceramente não pretendi não citar a referência .
Kin Shah
6

Desculpe, já estava trabalhando nessa resposta e prestes a postar quando a outra apareceu. Adicionando como wiki da comunidade apenas porque é uma abordagem um pouco diferente e adiciona um pouco de outras informações.

O 543066506c7cé essencialmente um hash da chave primária, e você pode recuperar essa linha (e potencialmente qualquer linha com uma colisão de hash) usando este SQL dinâmico:

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

Você pode fazer isso sem o SQL dinâmico, é claro, mas isso fornece um bom modelo para um trecho de código ou procedimento armazenado, no qual você pode apenas conectar os valores, se isso é algo que você soluciona muitos problemas. (Você também pode parametrizar o nome da tabela e também criar na análise da string KEY: para determinar tudo dinamicamente para você, mas achei que isso poderia estar fora do escopo desta publicação.)

Aaron Bertrand
fonte