sys.dm_tran_locks
DMVをクエリすると、テーブル、ページ、行などのリソースでロックを保持しているセッション(SPID)がわかります。
取得したロックごとに、どのSQLステートメント(削除、挿入、更新、または選択)がそのロックを引き起こしたかを判別する方法はありますか?
most_recent_query_handle
DMVのsys.dm_exec_connections
列は、最後に実行されたクエリのテキストを提供することを知っていますが、同じセッション(SPID)の下で他のクエリが数回実行され、まだロックを保持しています。
私はすでにsp_whoisactive
プロシージャ(Adam Machanicから)を使用しており、現時点で入力バッファにあるクエリのみを表示しています(DBCC INPUTBUFFER @spid
と考えてください)。ロックを取得したクエリです。
例えば:
sp_whoisactive
プロシージャは、ロックの原因ではないため、手順3のステートメントを指摘するため、役に立ちません。
この質問は、Blocked Process Reports機能を使用して分析を行い、本番環境でのブロッキングシナリオの根本的な原因を特定することから生じました。各トランザクションは複数のクエリを実行し、ほとんどの場合、最後のクエリ(BPRの入力バッファーに表示されます)がロックを保持するクエリになることはほとんどありません。
フォローアップの質問があります: ブロッキングクエリを効果的に識別するためのフレームワーク
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
2番目のウィンドウで、次のコマンドを実行します。
SELECT *
FROM dbo.TestLock
ここで、上からコミットされていないブロッキングトランザクションクエリを実行すると、次の出力が表示されます。
╔═══════════╦═══════════════════════════════╦═════ ═══════════════╦══════════════════════════════════ ═══════╗ ║SessionID║ItemType║BlockedBySessionID║QueryText║ ╠═══════════╬══════════ ═════════════════════╬════════════════════╬═══════ ══════════════════════════════════╣ ║67║トランザクション║0║BEGINトランザクション║ ║║║║挿入TO dbo.TestLock DEFAULT VALUES║ ║68║セッション要求、待機タスク║67║SELECT *║ ║║║║FROM 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 *║ ║║║║FROM dbo.TestLock; ║ ║68║セッション要求、待機中のタスク║67║SELECT *║ ║║║║FROM dbo.TestLock║ ╚══════════ ═╩═══════════════════════════════╩════════════════ ════╩════════════════════╝
1-完全ではない完全にtrue。 mayがロックを担当するステートメントを含むプロシージャキャッシュがあります。ただし、問題のリソースにアクセスする多くのクエリがキャッシュ内にある可能性があるため、ロックの実際の原因がどのステートメントであるかを判別するのは簡単ではない場合があります。
以下のクエリは、プロシージャキャッシュがあまりビジーではないため、上記のテストクエリのクエリプランを示しています。
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;
このクエリの結果mayを使用すると、原因を見つけることができますが、このようなプロシージャキャッシュの検査は、ビジー状態のシステムでは非常に困難な場合があることに注意してください。
2SQL Server 2016以降では Query Store が提供され、これはdoes実行されたクエリの完全な履歴を保持します。
マックスの答え を補完するために、私は以下のユーティリティが非常に便利であることを発見しました:
ブロッキングの詳細を調べ、ブロッキングが発生した原因と方法を分析したい場合は、beta_lockinfoを使用します。これは非常に便利です。
beta_lockinfoは、プロセスとプロセスが保持するロック、およびアクティブなトランザクションに関する情報を提供するストアドプロシージャです。 beta_lockinfoは、ブロッキングの状況に関する情報をできるだけ多く収集するように設計されているため、状況が絶望的である場合、犯人を即座に見つけて、ブロッキングプロセスを強制終了できます。次に、座り、beta_lockinfoからの出力を分析して、ブロック状況がどのように発生したかを理解し、状況が再発しないようにするためにどのようなアクションをとるかを理解します。 beta_lockinfoからの出力には、すべてのアクティブプロセスと、ロック付きのパッシブプロセス、ロックされているオブジェクト、最後に送信されたコマンド、および実行されているステートメントが表示されます。また、現在のステートメントのクエリプランも取得します。