Comment puis-je convertir une clé dans un rapport de blocage SQL Server en valeur?


15

J'ai un rapport de blocage qui me dit qu'il y a eu un conflit impliquant waitresource = "KEY: 9: 72057632651542528 (543066506c7c)" et je peux voir ceci:

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

dans <resource-list>. Je veux pouvoir trouver la valeur réelle de la clé (id = 12345, par exemple). Quelle instruction SQL dois-je utiliser pour obtenir ces informations?

Réponses:


9

Les réponses de @Kin, @AaronBertrand et @DBAFromTheCold sont excellentes et ont été très utiles. Une information importante que j'ai trouvée lors des tests que les autres réponses ont omis est que vous devez utiliser l'index qui est renvoyé sys.partitionspour le donné HOBT_IDlorsque vous recherchez le %%lockres%%(via un indice de requête d'index). Cet index n'est pas toujours l'index PK ou cluster.

Par exemple:

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

Voici un exemple de script modifié en utilisant des morceaux de chacune de ces réponses.

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;

La détermination automatique du nom de la base de données est une belle valeur ajoutée ici, avec l'indication d'index. Merci!
Mark Freeman

14

Vous avez le hobt_id donc la requête suivante identifiera la table: -

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

À partir de là, vous pouvez ensuite exécuter l'instruction suivante pour identifier la ligne du tableau (si elle existe toujours): -

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

Soyez prudent avec la déclaration ci-dessus, cependant, elle analysera la table cible, exécutez-la dans LIRE NON COMMIS et surveillez votre serveur.

Voici un article de Grant Fritchey sur %% LOCKRES %% - http://www.scarydba.com/2010/03/18/undocumented-virtual-column-lockres/

Et voici un article de mon propre blog sur l'utilisation de %% LOCKRES %% pour identifier les lignes d'un événement étendu: - https://dbafromthecold.wordpress.com/2015/02/24/identifying-blocking-via-extended-events/


Merci pour la réponse rapide et pour avoir inclus les liens vers les articles de blog utiles.
Mark Freeman

9

Le est un complément aux réponses déjà publiées par DBAFromTheCold et Aaron Bertrand .

Microsoft est toujours laissé %%lockres%%comme fonctionnalité non documentée .

Voici le script qui vous aidera :

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

Reportez-vous également à cet excellent article de blog sur: Le cas curieux du blocage douteux et du verrou pas si logique


Je choisis cela comme réponse. Bien que les solutions fournies par DBAFromTheCold et Aaron Bertrand fonctionnent toutes les deux, cela me permet d'obtenir les informations en ne fournissant que la clé, ce qui me rend plus efficace (bien qu'à une charge supplémentaire sur la base de données pour obtenir les informations que j'ai déjà, mais plutôt ne pas se rassembler pour fournir).
Mark Freeman

Kin, je pense que vous avez parcouru un long chemin ici, et je suis de plus en plus impressionné par vos réponses tout le temps. Cependant, vous devez révéler vos sources lorsque vous proposez du code écrit par quelqu'un d'autre (code identique ici , ici et ici ).
Aaron Bertrand

@AaronBertrand J'avais ce code depuis longtemps et je n'avais aucune référence, depuis que je l'utilisais. Merci puisque vous avez indiqué la référence (je l'ajouterai également dans mon référentiel). Merci aussi pour les aimables paroles! Je dois faire un long chemin pour apprendre et redonner à la communauté. Excuses et je ne voulais vraiment pas citer la référence .
Kin Shah

6

Désolé, je travaillais déjà sur cette réponse et sur le point de poster lorsque l'autre est apparu. Ajouter en tant que wiki communautaire uniquement parce que c'est une approche légèrement différente et ajoute un peu d'autres informations.

Le 543066506c7cest essentiellement un hachage de la clé primaire, et vous pouvez récupérer cette ligne (et potentiellement toutes les lignes avec une collision de hachage) à l'aide de ce SQL dynamique:

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

Vous pouvez le faire sans SQL dynamique, bien sûr, mais cela vous donne un joli modèle pour un extrait ou une procédure stockée où vous pouvez simplement brancher les valeurs, si c'est quelque chose que vous dépannez beaucoup. (Vous pouvez également paramétrer le nom de la table, et vous pouvez également créer une analyse syntaxique de la chaîne KEY: pour tout déterminer dynamiquement pour vous, mais j'ai pensé que cela pourrait être hors de portée pour ce post.)

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.