SQL Server のデッドロックグラフは、victim-list → process-list → resource-list の順で読むのが最短です。最初に「誰がロールバックされたか」を確認し、次に「どの SQL と接続元が関わったか」、最後に「何のリソースをどのロック モードで奪い合ったか」を追えば、原因の当たりはかなり早く付きます。大事なのは、犠牲になったセッションが必ずしも根本原因ではないことと、inputbuf だけで全体像を決めつけないことです。(Microsoft Learn)
デッドロックは、通常のブロッキングと違って待ち関係が輪になった状態です。SQL Server は循環依存を検出すると一方のトランザクションを被害者としてロールバックし、他方を先に進めます。デッドロックグラフは、その時点で「誰が何を持ち、何を待っていたか」を可視化したものです。(Microsoft Learn)
SQL Server 2012 以降では、収集手段として SQL Trace / Profiler の Deadlock Graph より xml_deadlock_report Extended Event が推奨です。SQL Server と Azure SQL Managed Instance では system_health セッションが既定でこれを収集しているため、まずは追加設定なしで確認できます。(Microsoft Learn)
最初に読む順番はこの3段階で十分
| 順番 | 見る場所 | ここで確定したいこと | ありがちな誤読 |
|---|---|---|---|
| 1 | victim-list | 誰がロールバックされたか | 被害者 = 原因と決めつける |
| 2 | process-list | どの SQL、どの接続元、どの分離レベルか | inputbuf の1文だけで全体を断定する |
| 3 | resource-list | どのオブジェクト・インデックス・ロックモードで競合したか | テーブル名だけ見て、indexname や mode を見ない |
この3段階は、deadlock wait-for graph の構成要素と process/resource ノードの定義に沿った読み方です。(Microsoft Learn)
SSMS でグラフを開いたら、まず関係した 2 つの process id を A/B とメモして、A が何を持ち、何を待ち、B が何を持ち、何を待っているかを左右で追うと混乱しません。矢印を眺めるより、A/B を紙に書き出したほうが早く理解できます。
victim-list では「犠牲者」と「犯人」を分けて考える
victim-list は、デッドロック解消のためにロールバックされたプロセスを示します。ただし、ここに出たセッションが「悪い処理」とは限りません。SQL Server は、まず DEADLOCK_PRIORITY の低いセッションを被害者にし、優先度が同じならロールバックコストの低い側を選びます。このコストは deadlock graph 上では logused として見られます。条件が同じならランダムに選ばれることもあります。(Microsoft Learn)
つまり、夜間バッチが毎回 victim になっているなら、「たまたま軽かった」か「意図的に低優先度にしている」可能性があります。逆に、ユーザー更新処理が victim になっているからといって、その処理が常に設計ミスとは言えません。まずは「誰が切られたか」と「なぜそのセッションが選ばれやすかったか」を分けて考えるのがポイントです。(Microsoft Learn)
業務上の優先順位が明確なら、SET DEADLOCK_PRIORITY で「どちらを負け役にするか」を調整するのは有効です。ただし、これは再発防止ではなく被害コントロールです。頻発する deadlock を priority だけで隠すと、根本原因の調査が後回しになります。(Microsoft Learn)
process-list は原因候補を絞る場所
inputbuf と executionStack で、まず実行中 SQL をつかむ
process-list では、各プロセスの inputbuf と executionStack を最優先で見ます。inputbuf はそのセッションで流れていたバッチや RPC の内容、executionStack は procname、line、sqlhandle など、どのストアドやどの行が関与したかを示す手掛かりです。ストアド プロシージャが絡む deadlock では、executionStack の procname と line が最短ルートになります。(Microsoft Learn)
ただし、inputbuf は万能ではありません。グラフに出るのは deadlock 発生時点の直近の文だけで、同じトランザクション内で先に実行された文が出ないことがあります。さらに、入力バッファーの表示は途中で切れることがあり、長い SQL をそのまま信用すると見誤ります。複数文トランザクションや長いストアドなら、アプリ側コード、プロシージャ本体、必要に応じて Extended Events の追加トレースまで見に行く前提で考えるほうが安全です。(Microsoft Learn)
環境によっては XML ビューに queryhash や queryplanhash が出ます。この値が見えるなら、Query Store と突き合わせて実行計画まで追うと、単なる「どの SQL がぶつかったか」から「なぜそのプランでロックが増えたか」まで一気に進めます。(Microsoft Learn)
isolationlevel で、不要にロックを長くしていないかを見る
isolationlevel は deadlock の読み解きでかなり重要です。READ COMMITTED では共有ロックは通常短時間ですが、REPEATABLE READ や SERIALIZABLE では読んだ行や範囲に対するロックが長く残ります。HOLDLOCK は SERIALIZABLE 相当なので、見た目はただの SELECT でも範囲ロックを長く保持していることがあります。(Microsoft Learn)
ここでの判断基準は単純です。更新系処理どうしがぶつかっているのか、それとも読取系がロックを長く握って更新系を巻き込んでいるのかを分けて考えます。SERIALIZABLE や HOLDLOCK が見えたら、「本当にその強さが必要か」を真っ先に疑って構いません。(Microsoft Learn)
clientapp hostname loginname は、どの経路の処理かを特定する材料
clientapp、hostname、loginname、spid は、似た SQL が複数経路から流れる現場で特に効きます。同じ UPDATE Orders でも、Web アプリから来たのか、ETL ジョブか、保守ツールかで対処は変わります。SQL 文だけで直すか、ジョブ時間帯をずらすか、アプリ側のトランザクション分割を検討するかは、この情報を見てから決めるべきです。deadlock XML にはこれらの接続識別情報が含まれます。(Microsoft Learn)
logused は「なぜこの victim になったか」の補助線
logused は、優先度が同じときにどちらが victim になりやすかったかを見る補助線です。数値が極端に小さい側が切られているなら、SQL Server が「こちらのほうが戻しやすい」と判断した可能性が高いです。根本原因の特定には直結しませんが、victim の見方を間違えないために確認しておく価値があります。(Microsoft Learn)
resource-list は「なぜぶつかったか」を読む場所
resource-list では、各リソースに対して owner-list と waiter-list が並びます。誰がそのリソースを持っていて、誰がどのモードで待っていたかを見ることで、循環依存の輪が見えてきます。ここではテーブル名だけでなく、objectname、indexname、mode をセットで見るのが鉄則です。(Microsoft Learn)
| 表示 | 意味 | 最初の読み方 |
|---|---|---|
KEY | インデックス上のキー | 行競合だけでなく、どのインデックスを経由したかを見る |
RID | ヒープ テーブルの物理行 | heap での更新や検索を疑う |
PAGE | データ/インデックスのページ | スキャンやページ単位の競合を疑う |
OBJECT / HOBT | テーブルやアクセスパス単位 | 粒度の粗いロックや内部アクセスパスを見る |
XACT | トランザクション ID リソース | optimized locking 環境の deadlock を疑う |
この整理は sys.dm_tran_locks の resource type 定義に沿ったものです。KEY は「テーブルの1行」というより「インデックス上のキー」で、RID は heap の物理行、XACT は optimized locking 有効時のトランザクション リソースです。(Microsoft Learn)
ロックモードは S U X と range lock をまず読めば足りる
S は共有ロック、U は更新準備の update lock、X は排他ロックです。U は「あとで更新するかもしれない」場面で使われ、同じリソースに複数セッションが同時に持てないのが重要な性質です。RangeS-U、RangeI-N、RangeX-X などが出たら、範囲ロックが絡んでいると考えてよく、SERIALIZABLE や HOLDLOCK の影響を優先的に確認します。(Microsoft Learn)
迷ったら、各リソースについて「A は何を持っているか」「B は何を待っているか」を 1 行ずつ書き出してください。たとえば、A が IX_Orders_Status の KEY を U で持ち、B が同じリソースを U で待っているなら、同じ更新候補に対する競合です。別リソースで逆向きの owner/waiter が見つかれば、それが deadlock の輪です。(Microsoft Learn)
パターン別に見ると原因が早い
S から X への変換が見えるなら、「読んでから更新する処理」を疑う
典型例は、2 つのトランザクションが同じ対象を先に読み、その後で更新しようとするパターンです。共有ロックを持ったまま双方が X へ変換しようとして、互いの S を待ち合うと deadlock になります。この種の deadlock は、REPEATABLE READ や SERIALIZABLE では特に起きやすく、READ COMMITTED でも起きないわけではありません。(Microsoft Learn)
optimized locking がない従来のロック モデルでは、「先に UPDLOCK で読み、その後更新する」パターンが有効なことがあります。U lock は1つのトランザクションしか持てないため、共通の S → X 変換 deadlock を避けやすいからです。反対に、optimized locking 有効環境では、広い範囲に lock hint を入れると利点を削るため、必要箇所に限定して使うべきです。(Microsoft Learn)
RangeS-U や RangeI-N が見えたら、SERIALIZABLE / HOLDLOCK を確認する
range lock は、行そのものではなく「その範囲に新しい行が入らないようにする」ためのロックです。SERIALIZABLE は一致した行だけでなく該当範囲のキーにもロックを置くため、更新系・挿入系と衝突しやすくなります。HOLDLOCK は SERIALIZABLE 相当なので、アプリやストアドで意識せず入っていると deadlock の温床になります。(Microsoft Learn)
同じテーブルなのに別インデックスが並ぶなら、アクセスパスを疑う
同じテーブルに対して PK_Orders と IX_Orders_Status のように複数インデックスが出るなら、片方が非クラスタ化インデックスで候補を集め、もう片方がベース行を取りに戻る Key Lookup(旧称 Bookmark Lookup)や大きな scan が関わっている可能性があります。こういうケースは deadlock graph だけで断定せず、必ず実行計画まで見てください。(Microsoft Learn)
実務上の優先順位としては、まずインデックス調整です。Microsoft も deadlock 再発防止の低リスクな第一手として nonclustered index のチューニングを挙げていますし、大きな scan や多数の bookmark lookup は lock footprint を増やし、deadlock を起こしやすくします。covering index を作れるなら、SQL の書き換えより先に効くことが少なくありません。(Microsoft Learn)
XACT が見えたら、optimized locking 前提で読む
XACT や <xactlock> が出る deadlock graph は、optimized locking が関与しているサインです。SQL Server 2025、Azure SQL Database、Azure SQL Managed Instance ではこの形があり、UnderlyingResource に実際の KEY や RID が入ります。見た目は従来の KEY deadlock と違いますが、読むべき実体は UnderlyingResource 側です。(Microsoft Learn)
optimized locking と RCSI が有効だと、行更新前の U lock を使わないケースがあり、従来の「U lock deadlock」とは読み筋が変わります。古い解説どおりに U を探しても見つからないなら、SQL Server のバージョンと database option を先に確認したほうが早いです。(Microsoft Learn)
直し方は「victim 調整」より「ロック量削減」が先
まずはインデックスを見直す
deadlock の再発防止で、最初に検討しやすく効果も出やすいのはインデックス調整です。目的は、スキャンを減らし、必要な行に早く到達し、余計な lookup を減らすことです。クエリを書き換えるより事故が少なく、再テスト範囲も比較的読みやすいので、運用現場では最優先にしやすい対策です。(Microsoft Learn)
トランザクションを短くする
大きな一括更新や削除を小さな単位に分割すると、保持ロック数も保持時間も減ります。ロック エスカレーションや広い範囲の競合も起きにくくなるため、deadlock 対策としても効きます。夜間メンテ系のストアドで deadlock が出るなら、1 回のトランザクション量を疑う価値が高いです。(Microsoft Learn)
不要な HOLDLOCK や高い分離レベルを外す
SERIALIZABLE や HOLDLOCK は本当に必要な場所だけに限定すべきです。オンプレミスの SQL Server や Azure SQL Managed Instance では、READ COMMITTED は既定で共有ロックを使う実装で、RCSI は既定で ON ではありません。一方、Azure SQL Database では RCSI が既定です。読取と更新の競合が主因なら、RCSI や snapshot 系の採用は有力ですが、ブロッキング前提で組まれたアプリでは意味論が変わるため、安易に切り替えず事前検証が必要です。(Microsoft Learn)
アプリ側のリトライも入れておく
deadlock はマルチユーザー環境では完全にゼロにできないことがあります。1205 エラーを受けたとき、アプリ側で短いランダム待機を挟んでリトライできるようにしておくと、単発 deadlock のユーザー影響を大きく減らせます。根本原因の除去とは別に、運用品質として入れておきたい対策です。(Microsoft Learn)
DEADLOCK_PRIORITY は「どちらを守るか」を決めるもの
ユーザー処理を優先し、集計や同期バッチを負け役にしたいなら DEADLOCK_PRIORITY は有効です。ただし、deadlock 自体を減らすわけではありません。頻度が高いのに priority だけで運用していると、いずれ業務影響の大きいタイミングで別の処理が victim になります。(Microsoft Learn)
収集方法は運用に合わせて選ぶ
| 方法 | 向く運用 | 強み | 注意点 |
|---|---|---|---|
system_health | まず1件見たいとき | 追加設定なしで確認しやすい | 保持容量に限りがあり、古いイベントは残り続けない |
専用 Extended Events (xml_deadlock_report) | 継続監視・再発調査 | 長期保存や条件整理に向く | セッション設計が必要 |
SQL Server Profiler / .xdl | 既存の legacy 運用や共有資料化 | .xdl を SSMS で開きやすい | 2012 以降の第一選択ではない |
trace flag 1204 / 1222 | 文字ログで補助確認したいとき | error log に出せる | 負荷の高い環境では推奨されない |
この選び分けは Microsoft の推奨に沿っています。xml_deadlock_report が基本で、SQL Server と Azure SQL Managed Instance では system_health が既定収集、Profiler は legacy 寄り、trace flag 1204 / 1222 は workload-intensive な環境では避けるべきとされています。(Microsoft Learn)
Azure SQL Database だけは前提が違います。built-in の system_health はなく、sqlserver.database_xml_deadlock_report を収集する専用 XEvents セッションを作る形です。SQL Server 本体と同じつもりで探すと見つからないので、クラウド環境ではここを最初に切り分けてください。(Microsoft Learn)
迷ったときは、この順で5分で読む
1つの deadlock graph を開いたら、次の順番でメモすると判断がぶれません。
victim-listで victim のprocess idを控えるprocess-listで A/B のinputbuf、executionStack、isolationlevel、clientappを抜き出すresource-listで各リソースのobjectname、indexname、mode、owner/waiterを対応付けるS/U/Xか、Range*か、XACTかでパターンを分類する- 対策を「インデックス」「トランザクション長」「分離レベル・hint」「アプリの retry」「priority 調整」のどれに置くか決める
この流れで読めば、deadlock graph を「難しい図」ではなく「修正方針を決めるための設計資料」に変えられます。(Microsoft Learn)
デッドロックグラフを読むときに本当に大切なのは、全部の属性を覚えることではありません。victim-list で被害者を確認し、process-list で SQL と接続経路を絞り、resource-list で競合したインデックスと lock mode を見る。この順番だけ守れば、次にやるべきことはかなり明確になります。まずは手元の1件を開いて、indexname、isolationlevel、inputbuf、logused の4つを書き出すところから始めてください。(Microsoft Learn)

コメント