ฉันจะแปลงคีย์ในรายงานการหยุดชะงักของ SQL Server เป็นค่าได้อย่างไร


15

ฉันมีรายงานการหยุดชะงักที่บอกฉันว่ามีข้อขัดแย้งเกี่ยวกับ waitresource = "KEY: 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%%(ผ่านคำใบ้ดัชนีแบบสอบถาม) ดัชนีนี้ไม่ได้เป็น PK หรือดัชนีคลัสเตอร์เสมอไป

ตัวอย่างเช่น:

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

การพิจารณาชื่อฐานข้อมูลโดยอัตโนมัติเป็นการเพิ่มมูลค่าที่ดีที่นี่พร้อมกับคำใบ้ดัชนี ขอบคุณ!
Mark Freeman

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)'

ระมัดระวังด้วยคำสั่งข้างต้น แต่มันจะสแกนตารางเป้าหมายเพื่อให้ทำงานใน READ UNCOMMITTED และตรวจสอบเซิร์ฟเวอร์ของคุณ

นี่คือบทความโดย Grant Fritchey เกี่ยวกับ %% 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/


ขอบคุณสำหรับการตอบสนองที่รวดเร็วและสำหรับการรวมลิงค์ไปยังโพสต์บล็อกที่เป็นประโยชน์
Mark Freeman

9

เป็นอาหารเสริมเพื่อตอบเอาไว้แล้วโดยDBAFromTheColdและอาโรนเบอร์ทรานด์

ไมโครซอฟท์ได้ออกยังคงเป็นคุณลักษณะที่ไม่มีเอกสาร%%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 จะช่วยให้ฉันได้รับข้อมูลโดยการให้กุญแจเท่านั้นทำให้มันมีประสิทธิภาพมากขึ้นสำหรับฉัน (แม้ว่าจะมีภาระเพิ่มเติมในฐานข้อมูลเพื่อรับข้อมูลที่ฉันมีอยู่แล้ว แต่จะ ค่อนข้างไม่ชิ้นรวมกันเพื่อให้)
Mark Freeman

ฉันคิดว่าคุณมาไกลแล้วและฉันก็ยิ่งประทับใจกับคำตอบของคุณตลอดเวลา แต่คุณควรเปิดเผยแหล่งที่มาของคุณ (s) เมื่อคุณเสนอโค้ดที่เขียนโดยคนอื่น (รหัสเหมือนกันที่นี่ , ที่นี่และที่นี่ )
Aaron Bertrand

@AaronBertrand ฉันมีรหัสนี้มาเป็นเวลานานและฉันไม่ได้มีการอ้างอิงใด ๆ เนื่องจากฉันได้ใช้มัน ขอบคุณเนื่องจากคุณชี้ให้เห็นการอ้างอิง (ฉันจะเพิ่มไว้ในที่เก็บของฉันเช่นกัน) นอกจากนี้ขอบคุณสำหรับคำใจดี! ฉันต้องเรียนรู้ไปนาน ๆ และคืนกลับสู่ชุมชน ขอโทษและฉันอย่างแท้จริงไม่ได้หมายความว่าจะได้กล่าวถึงการอ้างอิง
Kin Shah

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: สตริงเพื่อกำหนดทุกอย่างสำหรับคุณแบบไดนามิก แต่ฉันคิดว่ามันอาจอยู่นอกขอบเขตสำหรับโพสต์นี้)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.