¿Cómo puedo convertir una clave en un informe de punto muerto de SQL Server al valor?

15

Tengo un informe de punto muerto que me dice que hubo un conflicto relacionado con waitresource = "KEY: 9: 72057632651542528 (543066506c7c)" y puedo ver esto:

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

dentro de <resource-list>. Quiero poder encontrar el valor real de la clave (id = 12345, por ejemplo). ¿Qué instrucción SQL necesitaría usar para obtener esa información?

Mark Freeman
fuente

Respuestas:

9

Las respuestas de @Kin, @AaronBertrand y @DBAFromTheCold son geniales y fueron muy útiles. Una pieza importante de información que encontré durante las pruebas que las otras respuestas omitieron es que debe usar el índice que se devuelve sys.partitionspara lo dado HOBT_IDal buscar el %%lockres%%(a través de una pista de consulta de índice). Este índice no siempre es PK o índice agrupado.

Por ejemplo:

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

Aquí hay un script de ejemplo modificado usando piezas de cada una de estas respuestas.

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
fuente
Determinar automáticamente el nombre de la base de datos es un buen valor agregado aquí, junto con la sugerencia de índice. ¡Gracias!
Mark Freeman
14

Tiene hobt_id, por lo que la siguiente consulta identificará la tabla:

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 de eso, puede ejecutar la siguiente instrucción para identificar la fila en la tabla (si todavía existe):

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

Sin embargo, tenga cuidado con la declaración anterior, escaneará la tabla de destino, por lo que se ejecutará en LEER NO COMPROMETIDO y monitoreará su servidor.

Aquí hay un artículo de Grant Fritchey sobre %% LOCKRES %% - http://www.scarydba.com/2010/03/18/undocumented-virtual-column-lockres/

Y aquí hay un artículo de mi propio blog sobre el uso de %% LOCKRES %% para identificar filas de un evento extendido: - https://dbafromthecold.wordpress.com/2015/02/24/identifying-blocking-via-extended-events/

dbafromthecold
fuente
Gracias por la rápida respuesta y por incluir los enlaces a las útiles publicaciones del blog.
Mark Freeman
9

El es un complemento de las respuestas ya publicadas por DBAFromTheCold y Aaron Bertrand .

Microsoft todavía se ha ido %%lockres%%como característica no documentada .

A continuación se muestra el script que lo ayudará :

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 también esta excelente publicación de blog sobre: El curioso caso del estancamiento dudoso y el bloqueo no tan lógico

Kin Shah
fuente
Estoy eligiendo esto como la respuesta. Aunque las soluciones proporcionadas por DBAFromTheCold y Aaron Bertrand funcionan, esto me permite obtener la información solo al proporcionar la CLAVE, lo que me hace más eficiente (aunque con una carga adicional en la base de datos para obtener información que ya tengo, pero lo haría en lugar de no juntar para proporcionar).
Mark Freeman
Kin, creo que has recorrido un largo camino hasta aquí, y cada vez estoy más impresionado con tus respuestas. Sin embargo, debe revelar sus fuentes cuando proponga un código escrito por otra persona (código idéntico aquí , aquí y aquí ).
Aaron Bertrand
@AaronBertrand Tenía este código durante mucho tiempo y no tenía ninguna referencia, ya que lo he estado usando. Gracias porque señaló la referencia (también la agregaré en mi repositorio). Además, gracias por las amables palabras! Tengo que recorrer un largo camino aprendiendo y devolviendo a la comunidad. Disculpas y sinceramente no quise citar la referencia .
Kin Shah
6

Lo siento, ya estaba trabajando en esta respuesta y a punto de publicar cuando apareció la otra. Agregar como wiki de la comunidad solo porque es un enfoque ligeramente diferente y agrega un poco de otra información.

El 543066506c7ces esencialmente un hash de la clave primaria, y se puede recuperar esa fila (y, potencialmente, cualquier fila con una colisión de hash) utilizando 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;

Puede hacer esto sin SQL dinámico, por supuesto, pero esto le brinda una buena plantilla para un fragmento o procedimiento almacenado en el que simplemente puede conectar los valores, si esto es algo que soluciona mucho. (También podría parametrizar el nombre de la tabla, y también podría incorporar el análisis de la cadena KEY: para determinar todo de forma dinámica, pero pensé que podría estar fuera del alcance de esta publicación).

Aaron Bertrand
fuente