まだロックを保持しているクエリを見つける方法は?


15

sys.dm_tran_locksDMVを照会すると、テーブル、ページ、行などのリソースでロックを保持しているセッション(SPID)がわかります。

取得したロックごとに、どのSQLステートメント(削除、挿入、更新、または選択)がそのロックを引き起こしたかを判別する方法はありますか?

DMV のmost_recent_query_handle列にはsys.dm_exec_connections最後に実行されたクエリのテキストが表示されますが、他のクエリは同じセッション(SPID)で実行され、ロックを保持していることが何度かあります。

私はすでにsp_whoisactive(Adam Machanicの)プロシージャを使用していますが、現時点では入力バッファー上にあるクエリのみを表示しています(考えてみてください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 Textを見ると、そのステートメントが表示されていることがわかります。ただし、セッションがこれを行った場合:

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

2番目のウィンドウで、これを実行します。

SELECT *
FROM  dbo.TestLock

ここで、上からコミットされていないブロッキングトランザクションクエリを実行すると、次の出力が表示されます。

╔===========╦===================================== ===============╦================================ =======╗
║SessionID║ItemType║BlockedBySessionID║QueryText║
╠===========╬===================================== ===============╬================================ =======╣
║67║トランザクション║0║トランザクション開始║
bo║║║dbo.TestLockのデフォルト値に挿入║
║68║セッション要求、待機タスク║67║SELECT *║
bo║d bo dbo.TestLock║から
╚===========╩===================================== ===============╩================================ =======╝

(結果の最後から無関係な列を削除しました)。

ここで、最初のクエリウィンドウを次のように変更すると、

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

2番目のクエリウィンドウを再実行します。

SELECT *
FROM  dbo.TestLock

ブロッキングトランザクションクエリからの次の出力が表示されます。

╔===========╦===================================== ===============╦===================╗
║SessionID║ItemType║BlockedBySessionID║QueryText║
╠===========╬===================================== ===============╬===================╣
║67║トランザクション║0║SELECT *║
bo║d d dbo.TestLockから; ║
║68║セッション要求、待機タスク║67║SELECT *║
bo║d bo 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 Server 2016 以降に、実行されたクエリの完全な履歴を保持するクエリストアがあります。


@Maxに感謝します。この疑いはBlocked Process Reports、本番環境でのブロックシナリオの根本原因を見つけるために、機能の分析中に生まれました。各トランザクションは複数のクエリを実行しますが、ほとんどの場合、最後のクエリ(BPRの入力バッファに表示される)がロックを保持するクエリになることはほとんどありません。これを解決する最後のリソースは、軽量のxEventsセッションを設定して、各セッションで実行されたクエリを通知することです。この例を示す記事を知っているなら、感謝します。
タニテル

クエリストアについても非常に便利ですが、SPID情報がありません。とにかくありがとう。
tanitelle


6

マックスの答えを補完するために、以下のユーティリティが非常に有用であることがわかりました。

ブロッキングに深く入り込み、ブロッキングが何をどのように発生したかを分析したい場合、beta_lockinfoを使用します。これは非常に便利です。

beta_lockinfoは、プロセスおよびプロセスが保持するロックとアクティブなトランザクションに関する情報を提供するストアドプロシージャです。beta_lockinfoは、ブロッキング状況に関する情報をできるだけ多く収集するように設計されているため、状況が絶望的な場合は、即座に犯人を見つけてブロッキングプロセスを強制終了できます。その後、座ってbeta_lockinfoからの出力を分析し、ブロッキング状況がどのように発生したかを理解し、状況が再発しないようにするために実行するアクションを見つけます。beta_lockinfoの出力には、すべてのアクティブプロセスと、ロックのあるパッシブプロセス、ロックするオブジェクト、最後に送信したコマンド、実行しているステートメントが表示されます。また、現在のステートメントのクエリプランも取得します。


1
うわー、Erland Sommarskog procは素晴らしいです。
マックスヴァーノン

1
ええ..ブロッキングの詳細を深く掘り下げる必要があるときに使用します。
キンシャー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.