Bagaimana saya bisa mengkonversi kunci dalam laporan kebuntuan SQL Server ke nilai?

15

Saya memiliki laporan kebuntuan yang memberi tahu saya bahwa ada konflik yang melibatkan waitresource = "KEY: 9: 72057632651542528 (543066506c7c)" dan saya dapat melihat ini:

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

dalam <resource-daftar>. Saya ingin dapat menemukan nilai aktual untuk kunci (id = 12345, misalnya). Pernyataan SQL apa yang harus saya gunakan untuk mendapatkan informasi itu?

Mark Freeman
sumber

Jawaban:

9

Jawaban dari @Kin, @AaronBertrand, dan @DBAFromTheCold sangat bagus dan sangat membantu. Salah satu info penting yang saya temukan selama pengujian yang jawaban lain ditinggalkan adalah bahwa Anda perlu menggunakan indeks yang dikembalikan dari sys.partitionsuntuk diberikan HOBT_IDketika mencari %%lockres%%(melalui petunjuk permintaan indeks). Indeks ini tidak selalu indeks PK atau berkerumun.

Sebagai contoh:

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

Berikut ini contoh skrip yang dimodifikasi menggunakan potongan dari masing-masing jawaban ini.

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
sumber
Secara otomatis menentukan nama database adalah nilai tambah yang bagus di sini, bersama dengan petunjuk indeks. Terima kasih!
Mark Freeman
14

Anda memiliki hobt_id sehingga kueri berikut akan mengidentifikasi tabel: -

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

Dari itu Anda kemudian dapat menjalankan pernyataan berikut untuk mengidentifikasi baris dalam tabel (jika masih ada): -

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

Namun hati-hati dengan pernyataan di atas, itu akan memindai tabel target jadi jalankan di BACA TIDAK TERKESAN dan memantau server Anda.

Berikut ini artikel dari Grant Fritchey tentang %% LOCKRES %% - http://www.scarydba.com/2010/03/18/undocumented-virtual-column-lockres/

Dan inilah artikel dari blog saya sendiri tentang menggunakan %% LOCKRES %% untuk mengidentifikasi baris dari acara yang diperluas: - https://dbafromthecold.wordpress.com/2015/02/24/identifying-blocking-via-extended-events/

dbafromoldecold
sumber
Terima kasih atas tanggapan cepatnya, dan untuk menyertakan tautan ke posting blog yang bermanfaat.
Mark Freeman
9

Ini adalah suplemen untuk jawaban yang sudah diposting oleh DBAFromTheCold dan Aaron Bertrand .

Microsoft masih meninggalkan %%lockres%%sebagai fitur tidak berdokumen .

Di bawah ini adalah skrip yang akan membantu Anda :

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

Lihat juga posting blog yang luar biasa ini di: Kasus Penasaran tentang Kebuntuan yang Meragukan dan Kunci Tidak Begitu Logis

Kin Shah
sumber
Saya memilih ini sebagai jawabannya. Meskipun solusi yang diberikan oleh DBAFromTheCold dan Aaron Bertrand keduanya bekerja, ini memungkinkan saya untuk mendapatkan informasi dengan hanya menyediakan KUNCI, membuat ini lebih efisien bagi saya (walaupun pada beberapa beban tambahan pada database untuk mendapatkan informasi yang sudah saya miliki, tetapi akankah bukannya tidak menyatukan untuk menyediakan).
Mark Freeman
Kin, saya pikir Anda telah datang jauh di sini, dan saya menjadi semakin terkesan dengan jawaban Anda sepanjang waktu. Namun, Anda harus mengungkapkan sumber Anda ketika Anda mengusulkan kode yang ditulis oleh orang lain (kode identik di sini , di sini , dan di sini ).
Aaron Bertrand
@ AaronBertrand Saya memiliki kode ini untuk waktu yang lama dan saya tidak memiliki referensi, karena saya telah menggunakannya. Terima kasih karena Anda menunjukkan referensi (saya akan menambahkannya di repositori saya juga). Juga, terima kasih atas kata-kata baiknya! Saya harus belajar banyak dan memberikan kembali kepada masyarakat. Maaf dan saya benar-benar tidak bermaksud untuk tidak mengutip referensi .
Kin Shah
6

Maaf, sudah mengerjakan jawaban ini dan akan dikirim ketika yang lain muncul. Menambahkan sebagai komunitas wiki hanya karena pendekatannya sedikit berbeda dan menambahkan sedikit info lainnya.

Pada 543066506c7cdasarnya hash kunci utama, dan Anda dapat mengambil baris itu (dan berpotensi setiap baris dengan tabrakan hash) menggunakan SQL dinamis ini:

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

Anda dapat melakukan ini tanpa SQL dinamis, tentu saja, tetapi ini memberi Anda templat yang bagus untuk snippet atau prosedur tersimpan di mana Anda bisa mencolokkan nilainya, jika ini adalah sesuatu yang sering Anda atasi masalah. (Anda juga bisa parameterisasi nama tabel, dan Anda juga bisa membangun parsing dari KUNCI: string untuk menentukan semuanya untuk Anda secara dinamis, tapi saya pikir itu mungkin di luar jangkauan untuk posting ini.)

Aaron Bertrand
sumber