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주어진 색인에서 사용해야하는 색인을 사용해야한다는 것입니다 . 이 인덱스가 항상 PK 또는 클러스터형 인덱스 인 것은 아닙니다.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;

데이터베이스 이름을 자동으로 결정하는 것은 색인 힌트와 함께 여기에 유용한 부가 가치입니다. 감사!
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)'

그러나 위의 명령문에주의하십시오. 대상 테이블을 스캔하여 커밋되지 않은 읽기에서 실행되고 서버를 모니터링합니다.

- 여기 %% LOCKRES %%에 대한 그랜트 Fritchey의 글입니다 http://www.scarydba.com/2010/03/18/undocumented-virtual-column-lockres/은

그리고 내 블로그의 % % LOCKRES % %를 사용하여 확장 된 이벤트의 행을 식별하는 방법에 대한 기사가 있습니다.


빠른 답변과 유용한 블로그 게시물에 대한 링크를 포함 해 주셔서 감사합니다.
Mark Freeman

9

이것은 DBAFromTheColdAaron Bertrand가 이미 게시 한 답변에 대한 보충 자료 입니다.

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 만 제공하여 정보를 얻을 수 있으므로 더 효율적입니다. 오히려 함께 제공하지 않습니다).
마크 프리먼

킨, 나는 당신이 여기에 먼 길을 왔다고 생각하며, 나는 항상 당신의 대답에 점점 더 깊은 인상을받습니다. 그러나 다른 사람이 작성한 코드 ( 여기 , 여기여기에 동일한 코드)를 제안 할 때는 소스를 공개해야합니다 .
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.