여전히 잠금을 보유하고있는 쿼리를 찾는 방법은 무엇입니까?


15

sys.dm_tran_locksDMV를 쿼리하면 테이블, 페이지 및 행과 같은 리소스에 대한 잠금을 보유하고있는 세션 (SPID)이 표시됩니다.

획득 한 각 잠금에 대해 잠금을 유발 한 SQL 문 (삭제, 삽입, 업데이트 또는 선택)을 판별하는 방법이 있습니까?

DMV 의 most_recent_query_handle열은 sys.dm_exec_connections마지막으로 실행 된 쿼리의 텍스트를 제공하지만 다른 쿼리가 동일한 세션 (SPID)에서 실행되어 여전히 잠금을 유지하고 있다는 것을 알고 있습니다.

이미 sp_whoisactive(Adam Machanic의) 절차를 사용하고 있으며 현재 입력 버퍼에있는 쿼리 (think DBCC INPUTBUFFER @spid) 만 표시합니다. 항상 (그리고 제 경우에는 절대로) 잠금을 획득 한 쿼리는 아닙니다.

예를 들면 다음과 같습니다.

  1. 공개 거래 / 세션
  2. 명령문 실행 (자원에 대한 잠금 보유)
  3. 같은 세션에서 다른 진술을 집행하다
  4. 다른 트랜잭션 / 세션을 열고 2 단계에서 잠근 리소스를 수정하십시오.

sp_whoisactive절차는 유용하므로 잠금에 대한 책임, 그리고없는 3 단계에서 문을 지적 할 것이다.

이 질문은 프로덕션에서 차단 시나리오의 근본 원인을 찾기 위해 차단 된 프로세스 보고서 기능을 사용하여 분석을 수행 한 것 입니다. 각 트랜잭션은 여러 쿼리를 실행하며 대부분 마지막 트랜잭션 (BPR의 입력 버퍼에 표시됨)은 잠금을 유지하는 트랜잭션이 아닙니다.

후속 질문이 있습니다. 차단 쿼리를 효과적으로 식별하는 프레임 워크

답변:


15

SQL Server는 1,2 실행 된 명령의 기록을 유지하지 않습니다 . 잠금이있는 오브젝트를 판별 할 수 있지만 잠금을 유발 한 명령문을 반드시 볼 수는 없습니다 .

예를 들어이 명령문을 실행하면

BEGIN TRANSACTION
INSERT INTO dbo.TestLock DEFAULT VALUES

그리고 가장 최근의 SQL 핸들을 통해 SQL 텍스트를 보면 명령문이 표시됩니다. 그러나 세션에서이 작업을 수행 한 경우 :

BEGIN TRANSACTION
INSERT INTO dbo.TestLock DEFAULT VALUES
GO
SELECT *
FROM dbo.TestLock;
GO

SELECT * FROM dbo.TestLock;트랜잭션이 커밋되지 않았고 INSERT명령문이 dbo.TestLock테이블 에 대한 독자를 차단 하고 있지만 명령문 만 표시 됩니다.

다른 세션을 차단하는 커밋되지 않은 트랜잭션을 찾는 데 사용합니다.

/*
    This query shows sessions that are blocking other sessions, including sessions that are 
    not currently processing requests (for instance, they have an open, uncommitted transaction).

    By:  Max Vernon, 2017-03-20
*/
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; --reduce possible blocking by this query.

USE tempdb;

IF OBJECT_ID('tempdb..#dm_tran_session_transactions') IS NOT NULL
DROP TABLE #dm_tran_session_transactions;
SELECT *
INTO #dm_tran_session_transactions
FROM sys.dm_tran_session_transactions;

IF OBJECT_ID('tempdb..#dm_exec_connections') IS NOT NULL
DROP TABLE #dm_exec_connections;
SELECT *
INTO #dm_exec_connections
FROM sys.dm_exec_connections;

IF OBJECT_ID('tempdb..#dm_os_waiting_tasks') IS NOT NULL
DROP TABLE #dm_os_waiting_tasks;
SELECT *
INTO #dm_os_waiting_tasks
FROM sys.dm_os_waiting_tasks;

IF OBJECT_ID('tempdb..#dm_exec_sessions') IS NOT NULL
DROP TABLE #dm_exec_sessions;
SELECT *
INTO #dm_exec_sessions
FROM sys.dm_exec_sessions;

IF OBJECT_ID('tempdb..#dm_exec_requests') IS NOT NULL
DROP TABLE #dm_exec_requests;
SELECT *
INTO #dm_exec_requests
FROM sys.dm_exec_requests;

;WITH IsolationLevels AS 
(
    SELECT v.*
    FROM (VALUES 
              (0, 'Unspecified')
            , (1, 'Read Uncomitted')
            , (2, 'Read Committed')
            , (3, 'Repeatable')
            , (4, 'Serializable')
            , (5, 'Snapshot')
        ) v(Level, Description)
)
, trans AS 
(
    SELECT dtst.session_id
        , blocking_sesion_id = 0
        , Type = 'Transaction'
        , QueryText = dest.text
    FROM #dm_tran_session_transactions dtst 
        LEFT JOIN #dm_exec_connections dec ON dtst.session_id = dec.session_id
    OUTER APPLY sys.dm_exec_sql_text(dec.most_recent_sql_handle) dest
)
, tasks AS 
(
    SELECT dowt.session_id
        , dowt.blocking_session_id
        , Type = 'Waiting Task'
        , QueryText = dest.text
    FROM #dm_os_waiting_tasks dowt
        LEFT JOIN #dm_exec_connections dec ON dowt.session_id = dec.session_id
    OUTER APPLY sys.dm_exec_sql_text(dec.most_recent_sql_handle) dest
    WHERE dowt.blocking_session_id IS NOT NULL
)
, requests AS 
(
SELECT des.session_id
    , der.blocking_session_id
    , Type = 'Session Request'
    , QueryText = dest.text
FROM #dm_exec_sessions des
    INNER JOIN #dm_exec_requests der ON des.session_id = der.session_id
OUTER APPLY sys.dm_exec_sql_text(der.sql_handle) dest
WHERE der.blocking_session_id IS NOT NULL
    AND der.blocking_session_id > 0 
)
, Agg AS (
    SELECT SessionID = tr.session_id
        , ItemType = tr.Type
        , CountOfBlockedSessions = (SELECT COUNT(*) FROM requests r WHERE r.blocking_session_id = tr.session_id)
        , BlockedBySessionID = tr.blocking_sesion_id
        , QueryText = tr.QueryText
    FROM trans tr
    WHERE EXISTS (
        SELECT 1
        FROM requests r
        WHERE r.blocking_session_id = tr.session_id
        )
    UNION ALL
    SELECT ta.session_id
        , ta.Type
        , CountOfBlockedSessions = (SELECT COUNT(*) FROM requests r WHERE r.blocking_session_id = ta.session_id)
        , BlockedBySessionID = ta.blocking_session_id
        , ta.QueryText
    FROM tasks ta
    UNION ALL
    SELECT rq.session_id
        , rq.Type
        , CountOfBlockedSessions =  (SELECT COUNT(*) FROM requests r WHERE r.blocking_session_id = rq.session_id)
        , BlockedBySessionID = rq.blocking_session_id
        , rq.QueryText
    FROM requests rq
)
SELECT agg.SessionID
    , ItemType = STUFF((SELECT ', ' + COALESCE(a.ItemType, '') FROM agg a WHERE a.SessionID = agg.SessionID ORDER BY a.ItemType FOR XML PATH ('')), 1, 2, '')
    , agg.BlockedBySessionID
    , agg.QueryText
    , agg.CountOfBlockedSessions
    , des.host_name
    , des.login_name
    , des.is_user_process
    , des.program_name
    , des.status
    , TransactionIsolationLevel = il.Description
FROM agg 
    LEFT JOIN #dm_exec_sessions des ON agg.SessionID = des.session_id
    LEFT JOIN IsolationLevels il ON des.transaction_isolation_level = il.Level
GROUP BY agg.SessionID
    , agg.BlockedBySessionID
    , agg.CountOfBlockedSessions
    , agg.QueryText
    , des.host_name
    , des.login_name
    , des.is_user_process
    , des.program_name
    , des.status
    , il.Description
ORDER BY 
    agg.BlockedBySessionID
    , agg.CountOfBlockedSessions
    , agg.SessionID;

몇 개의 쿼리 창을 사용하여 SSMS에 간단한 테스트 베드를 설정하면 가장 최근에 활성화 된 명령문 만 볼 수 있습니다.

첫 번째 쿼리 창에서 다음을 실행하십시오.

CREATE TABLE dbo.TestLock
(
    id int NOT NULL IDENTITY(1,1)
);
BEGIN TRANSACTION
INSERT INTO dbo.TestLock DEFAULT VALUES

두 번째 창에서 다음을 실행하십시오.

SELECT *
FROM  dbo.TestLock

커밋되지 않은 차단 트랜잭션 쿼리를 위에서 실행하면 다음과 같은 결과가 나타납니다.

╔ ============ ╦ ======================================= ================ ╦ =================================== ======== ╗
ID SessionID ║ 항목 유형 ║ BlockedBySessionID ║ QueryText ║
╠ ============ ╬ ======================================= ================ ╬ =================================== ======== ╣
║ 67 ║ 거래 ║ 0 EG 거래 시작 ║
bo ║ DE ║ dbo.TestLock 기본 값에 삽입 ║
Request 68 ║ 세션 요청, 대기 작업 ║ 67 ║ SELECT * ║
dbo.TestLock에서 ║ ║ ║ ║
╚ ============ ╩ ======================================= ================ ╩ =================================== ======== ╝

(결과의 끝에서 관련이없는 열을 제거했습니다).

이제 첫 번째 쿼리 창을 다음과 같이 변경하면

BEGIN TRANSACTION
INSERT INTO dbo.TestLock DEFAULT VALUES
GO
SELECT *
FROM dbo.TestLock;
GO

두 번째 쿼리 창을 다시 실행하십시오.

SELECT *
FROM  dbo.TestLock

차단 트랜잭션 쿼리에서이 출력을 볼 수 있습니다.

╔ ============ ╦ ======================================= ================= ╦ =======================
ID SessionID ║ 항목 유형 ║ BlockedBySessionID ║ QueryText ║
╠ ============ ╬ ======================================= ================= ╬ =======================
║ 67 ║ 거래 ║ 0 ║ 선택 * ║
bo ║ ║ ║ FROM dbo.TestLock; ║
Request 68 ║ 세션 요청, 대기 작업 ║ 67 ║ SELECT * ║
dbo.TestLock에서 ║ ║ ║ ║
╚ ============ ╩ ======================================= ================= ╩ =======================

1- 완전히 사실이 아닙니다 . 잠금을 담당하는 명령문을 포함 할 있는 프로 시저 캐시 있습니다. 그러나 캐시에 문제의 리소스에 닿는 쿼리가 많을 수 있으므로 어떤 문이 잠금의 실제 원인인지 판단하기가 쉽지 않을 수 있습니다.

아래 쿼리는 프로 시저 캐시 사용량이 많지 않기 때문에 위의 테스트 쿼리에 대한 쿼리 계획을 보여줍니다.

SELECT TOP(30) t.text
    , p.query_plan
    , deqs.execution_count
    , deqs.total_elapsed_time
    , deqs.total_logical_reads
    , deqs.total_logical_writes
    , deqs.total_logical_writes
    , deqs.total_rows
    , deqs.total_worker_time
    , deqs.*
FROM sys.dm_exec_query_stats deqs
OUTER APPLY sys.dm_exec_sql_text(deqs.sql_handle) t 
OUTER APPLY sys.dm_exec_query_plan(deqs.plan_handle) p
WHERE t.text LIKE '%dbo.TestLock%'  --change this to suit your needs
    AND t.text NOT LIKE '/\/\/\/\/EXCLUDE ME/\/\/\/\/\'
ORDER BY 
    deqs.total_worker_time DESC;

이 쿼리의 결과는 같은 프로 시저 캐시는 매우 바쁜 시스템에 요구 될 수 있습니다 검사, 당신은 범인을 찾을 수 있지만주의해야합니다.

2 SQL 서버 2016 이상 제공하는 쿼리 스토어 , 수행 실행되는 쿼리의 전체 역사를 유지합니다.


@Max에게 감사드립니다. Blocked Process Reports프로덕션에서 차단 시나리오의 근본 원인을 찾기 위해 기능 을 분석하는 동안 이러한 의심이 발생했습니다. 각 트랜잭션은 여러 쿼리를 실행하며 대부분 마지막 트랜잭션 (BPR의 입력 버퍼에 표시됨)은 잠금을 유지하는 트랜잭션이 아닙니다. 이 문제를 해결하기위한 마지막 리소스는 간단한 xEvents 세션을 설정하여 각 세션에서 어떤 쿼리가 실행되었는지 알려주는 것 같습니다. 이 예제를 보여주는 기사를 알고 있다면 감사하겠습니다.
tanitelle

또한 Query Store와 관련하여 매우 유용하지만 SPID 정보가 없습니다. 어쨌든 고마워
tanitelle


6

Max의 답변 을 보완하기 위해 아래 유틸리티가 매우 유용하다는 것을 알았습니다.

차단에 대해 자세히 알아보고 차단 방법과 차단 방법을 분석 할 때 beta_lockinfo를 사용합니다. 이는 매우 유용합니다.

beta_lockinfo는 프로세스 및 프로세스가 보유한 잠금 및 활성 트랜잭션에 대한 정보를 제공하는 저장 프로 시저입니다. beta_lockinfo는 가능한 한 차단 상황에 대한 많은 정보를 수집하도록 설계되어 상황이 절실한 경우 범인을 즉시 찾아 차단 프로세스를 종료 할 수 있습니다. 그런 다음 앉아서 beta_lockinfo의 출력을 분석하여 블로킹 상황이 어떻게 발생했는지 이해하고 상황이 재발하지 않도록 조치를 취할 수 있습니다. beta_lockinfo의 출력은 잠금이있는 수동 프로세스뿐만 아니라 잠금이있는 수동 프로세스, 마지막으로 제출 한 명령 및 실행중인 명령문을 보여줍니다. 현재 명령문에 대한 쿼리 계획도 얻습니다.


1
와, 그 Erland Sommarskog proc는 훌륭합니다.
Max Vernon

1
그래 .. 나는 세부 사항을 차단하기 위해 깊이 잠수해야 할 때 사용합니다.
Kin Shah
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.