Як я можу перетворити ключ у звіті про тупик SQL Server у значення?


15

У мене є звіт про глухий кут, який повідомляє мені, що стався конфлікт, пов’язаний з waitresource = "КЛЮЧ: 9: 72057632651542528 (543066506c7c)", і я можу це бачити:

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

в межах <resource-list>. Я хочу мати можливість знайти фактичне значення для ключа (наприклад, id = 12345). Який оператор SQL мені потрібно використовувати, щоб отримати цю інформацію?

Відповіді:


9

Відповіді @Kin, @AaronBertrand та @DBAFromTheCold чудові та були дуже корисними. Однією важливою інформацією, яку я виявив під час тестування, що інші відповіді залишились осторонь, - це те, що вам потрібно використовувати індекс, який повертається sys.partitionsдля даного HOBT_IDпід час пошуку %%lockres%%(через підказку щодо запиту індексу). Цей індекс не завжди є індексом ПК або кластером.

Наприклад:

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

Ось приклад сценарію, модифікованого за допомогою фрагментів з кожної з цих відповідей.

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;

Автоматичне визначення імені бази даних є приємною добавкою тут, поряд із натяком на індекс. Спасибі!
Марк Фрімен

14

У вас є hobt_id, тому наступний запит визначить таблицю: -

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

Після цього ви можете запустити наступне твердження, щоб визначити рядок у таблиці (якщо він все ще існує): -

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

Будьте обережні з вищезазначеним твердженням, однак воно просканує цільову таблицю, тому запустіть її в режимі ЧИТАТИ НЕЗАКОМНОГО та контролюйте ваш сервер.

Ось стаття Гранта Фрітчі про %% LOCKRES %% - http://www.scarydba.com/2010/03/18/undocumented-virtual-column-lockres/

І ось стаття з мого власного блогу про використання %% LOCKRES %% для ідентифікації рядків із розширеної події: - https://dbafromthecold.wordpress.com/2015/02/24/identifying-blocking-via-extended-events/


Дякуємо за швидку відповідь та за включення посилань на корисні публікації блогу.
Марк Фрімен

9

Це доповнення до відповідей, які вже опублікували DBAFromTheCold та Аарон Бертран .

Microsoft як і раніше залишається %%lockres%%недокументованою функцією .

Нижче наведено сценарій, який допоможе вам :

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

Також зверніться до цієї чудової публікації в блозі на тему: Цікавий випадок сумнівної тупикової ситуації та не дуже логічний замок


Я вибираю це як відповідь. Хоча рішення, надані DBAFromTheCold та Aaron Bertrand, працюють, це дозволяє мені отримувати інформацію, лише надаючи KEY, що робить це більш ефективним для мене (хоча при деякому додатковому навантаженні в базі даних, щоб отримати інформацію, яку я вже маю, але швидше не складати разом, щоб надати).
Марк Фрімен

Кін, я думаю, ти пройшов довгий шлях сюди, і я весь час все більше вражаю твої відповіді. Однак ви повинні виявити свої джерела, коли ви пропонуєте код, написаний кимось іншим (ідентичний код тут , тут і тут ).
Аарон Бертран

@AaronBertrand У мене був цей код давно, і я не мав жодної посилання, оскільки я ним користувався. Дякую, що ви вказали на посилання (я також додам його до свого сховища). Також дякую за добрі слова! Мені належить пройти довгий шлях навчання та повернення до громади. Вибачтесь, і я, правда, не мав на увазі не цитувати посилання .
Кін Шах

6

Вибачте, я вже працював над цією відповіддю і збирався розмістити повідомлення, коли з’явився інший. Додавання як вікі спільноти лише тому, що це дещо інший підхід і додає трохи іншої інформації.

По 543066506c7cсуті це хеш основного ключа, і ви можете отримати цей рядок (і, можливо, будь-які рядки з хеш-зіткненням), використовуючи цей динамічний SQL:

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

Це, звичайно, можна зробити без динамічного SQL, але це дає приємний шаблон для фрагмента або збереженої процедури, де ви можете просто підключити значення, якщо це щось, з чим ви багато проблем. (Ви також можете параметризувати назву таблиці, а також можете побудувати при розборі рядка KEY: щоб динамічно визначити все для вас, але я подумав, що це може бути поза рамками для цієї публікації.)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.