【SQL】ロック調査・解除完全ガイド|SHOW PROCESSLIST・pg_locks・sys.dm_tran_locks・KILLコマンドまで

【SQL】ロック調査・解除完全ガイド|SHOW PROCESSLIST・pg_locks・sys.dm_tran_locks・KILLコマンドまで SQL

「クエリが応答しない」「UPDATE が終わらない」「アプリがハングした」——こうした症状の多くはロック競合が原因です。ロックが発生したとき、まず「どのセッションが何をロックしているのか」を特定し、必要であれば解除するのが運用の基本です。

本記事では MySQL・PostgreSQL・SQL Server ごとに、ロック状態を調査する具体的なSQLと、セッションを終了させるコマンドを体系的に解説します。

この記事で分かること

  • MySQL でロックしているセッションを特定する(performance_schema・sys ビュー)
  • PostgreSQL で pg_locks と pg_stat_activity を使ってブロッキングを調べる
  • SQL Server で sys.dm_tran_locks + sys.dm_exec_sessions を結合してロックを特定する
  • 各RDBMSで問題のセッションを KILL する方法
  • ロック調査の手順フローとよくある原因パターン
スポンサーリンク

ロック調査の全体像

ロック調査は「原因セッションの特定 → ロックの詳細確認 → 解除」の3ステップで進めます。

ステップ MySQL PostgreSQL SQL Server
①実行中クエリ確認 SHOW PROCESSLIST pg_stat_activity sys.dm_exec_requests
②ロック状態確認 performance_schema.data_locks pg_locks sys.dm_tran_locks
③待機関係の確認 sys.innodb_lock_waits pg_blocking_pids() sys.dm_os_waiting_tasks
④セッション終了 KILL [id] pg_terminate_backend(pid) KILL [spid]

調査前に確認:ロックの「仕組み」(S/Xロック互換性、SELECT FOR UPDATE の動作など)についてはテーブル・行ロック完全ガイドを参照してください。本記事は「発生しているロックを調査・解除する」運用手順に特化しています。

MySQL でロックを調査する

STEP1:実行中クエリを確認する

まず SHOW PROCESSLIST で現在何が動いているかを確認します。Time 列が大きいものや StateWaiting for lock になっているものが怪しいセッションです。

SHOW PROCESSLIST — 実行中セッション一覧
-- 現在のセッション一覧(クエリが長い場合は短縮表示)
SHOW PROCESSLIST;

-- 完全なクエリ文を表示(FULL付き)
SHOW FULL PROCESSLIST;

-- 情報スキーマ版(より詳細・WHERE で絞り込み可能)
SELECT
    id,
    user,
    host,
    db,
    command,
    time          AS elapsed_sec,
    state,
    LEFT(info, 100) AS query
FROM information_schema.processlist
WHERE command != 'Sleep'
ORDER BY time DESC;

注目すべき State の値:

State の値 意味
Waiting for metadata lock DDL(ALTER TABLE など)がメタデータロックを待っている
Waiting for table level lock テーブルレベルのロックを待っている
Updating UPDATE/DELETE 実行中(長時間なら要確認)
Locked MyISAM 系のロック待ち(InnoDB は表示されない)

STEP2:InnoDB のトランザクションとロックを確認する(MySQL 8.0+)

MySQL 8.0 以降は performance_schema.data_locksdata_lock_waits が推奨です(旧バージョンは information_schema.innodb_locks を使用)。

進行中トランザクションを確認(INNODB_TRX)
-- 現在アクティブな InnoDB トランザクション一覧
SELECT
    trx_id,
    trx_state,
    trx_started,
    TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS elapsed_sec,
    trx_query,
    trx_rows_locked,
    trx_rows_modified,
    trx_mysql_thread_id   -- PROCESSLIST の id と対応
FROM information_schema.innodb_trx
ORDER BY trx_started;
行ロックの詳細確認(MySQL 8.0+)
-- どのトランザクションがどの行をロックしているか
SELECT
    dl.engine_lock_id,
    dl.engine_transaction_id,
    dl.object_schema,
    dl.object_name       AS table_name,
    dl.index_name,
    dl.lock_type,        -- RECORD / TABLE
    dl.lock_mode,        -- X, S, IX, IS, GAP など
    dl.lock_status       -- GRANTED / WAITING
FROM performance_schema.data_locks dl
ORDER BY dl.object_name, dl.lock_status;
ロック待ちの依存関係を確認
-- どのトランザクションが誰を待っているか(MySQL 8.0+)
SELECT
    wait.requesting_engine_transaction_id  AS waiting_trx,
    wait.blocking_engine_transaction_id    AS blocking_trx,
    dl_wait.object_name                    AS table_name,
    dl_wait.lock_mode                      AS waiting_mode,
    dl_blk.lock_mode                       AS blocking_mode
FROM performance_schema.data_lock_waits wait
JOIN performance_schema.data_locks dl_wait
    ON wait.requesting_engine_lock_id = dl_wait.engine_lock_id
JOIN performance_schema.data_locks dl_blk
    ON wait.blocking_engine_lock_id = dl_blk.engine_lock_id;
sys.innodb_lock_waits — 人が読みやすい形式(MySQL 8.0+)
-- sys スキーマを使った一発確認クエリ(管理者向けに最適化済み)
SELECT
    wait_started,
    wait_age,
    waiting_query,
    blocking_pid,
    blocking_query,
    blocking_trx_started,
    blocking_trx_age
FROM sys.innodb_lock_waits
ORDER BY wait_age DESC;

MySQL 5.7 以前の場合:performance_schema.data_locks の代わりに information_schema.INNODB_LOCKSINNODB_LOCK_WAITS を使います。MySQL 8.0 でこれらは削除されました。

MySQL 5.7 以前のロック確認
-- MySQL 5.7 以前のみ有効(8.0で削除)
SELECT r.trx_id        AS waiting_trx_id,
       r.trx_mysql_thread_id AS waiting_thread,
       r.trx_query     AS waiting_query,
       b.trx_id        AS blocking_trx_id,
       b.trx_mysql_thread_id AS blocking_thread,
       b.trx_query     AS blocking_query
FROM information_schema.innodb_lock_waits w
JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id
JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id;

STEP3:メタデータロック(MDL)の確認

ALTER TABLE などのDDLが止まっているとき、実行中のSELECTやトランザクションがメタデータロックを保持していることがあります。

メタデータロックの待ち状況を確認
-- performance_schema でメタデータロック待ちを確認
SELECT
    waiting.processlist_id     AS waiting_pid,
    waiting.processlist_info   AS waiting_query,
    blocking.processlist_id    AS blocking_pid,
    blocking.processlist_info  AS blocking_query
FROM performance_schema.metadata_locks ml_wait
JOIN performance_schema.metadata_locks ml_blk
    ON ml_wait.object_schema = ml_blk.object_schema
    AND ml_wait.object_name  = ml_blk.object_name
    AND ml_wait.lock_status  = 'PENDING'
    AND ml_blk.lock_status   = 'GRANTED'
JOIN performance_schema.threads waiting
    ON ml_wait.owner_thread_id = waiting.thread_id
JOIN performance_schema.threads blocking
    ON ml_blk.owner_thread_id  = blocking.thread_id;

STEP4:問題セッションを KILL する

KILL でセッションを終了
-- PROCESSLIST の id を指定して終了(接続ごと切断)
KILL 1234;

-- クエリのみ中断(接続は維持)
KILL QUERY 1234;

-- 複数まとめて実行する場合(動的SQL)
SELECT CONCAT('KILL ', id, ';')
FROM information_schema.processlist
WHERE time > 300          -- 300秒以上実行中
  AND command != 'Sleep'
  AND user != 'root';     -- 管理者は除外
-- 出力をコピー&実行する

KILL の注意:KILL すると対象セッションのトランザクションはロールバックされます。更新中であれば変更は取り消されます。影響範囲を確認してから実行してください。本番環境では特に慎重に。

PostgreSQL でロックを調査する

STEP1:実行中クエリとロック待ちを確認する

pg_stat_activity — 実行中セッション一覧
-- 現在アクティブなセッション(アイドルを除く)
SELECT
    pid,
    usename         AS user_name,
    application_name,
    client_addr,
    state,
    wait_event_type,
    wait_event,
    EXTRACT(EPOCH FROM (NOW() - query_start))::INT AS elapsed_sec,
    LEFT(query, 120)  AS query
FROM pg_stat_activity
WHERE state != 'idle'
  AND pid  != pg_backend_pid()   -- 自分自身を除外
ORDER BY elapsed_sec DESC NULLS LAST;
ロック待ちのセッションだけ絞り込む
-- wait_event_type = 'Lock' のセッションを抽出
SELECT
    pid,
    usename,
    wait_event_type,
    wait_event,
    state,
    EXTRACT(EPOCH FROM (NOW() - state_change))::INT AS wait_sec,
    LEFT(query, 200) AS query
FROM pg_stat_activity
WHERE wait_event_type = 'Lock'
ORDER BY wait_sec DESC;

STEP2:pg_locks でロックの詳細を確認する

pg_locks — ロック状態の確認
-- ロック一覧(テーブル名付き)
SELECT
    l.pid,
    l.locktype,
    l.relation::regclass AS table_name,
    l.mode,
    l.granted,
    l.transactionid
FROM pg_locks l
WHERE l.relation IS NOT NULL   -- リレーション(テーブル等)のロックのみ
ORDER BY l.granted, l.table_name;
ブロッキング関係の特定(推奨クエリ)
-- どの PID が誰をブロックしているかを一目で確認
SELECT
    blocked.pid          AS waiting_pid,
    blocked.usename      AS waiting_user,
    LEFT(blocked.query, 80) AS waiting_query,
    blocking.pid         AS blocking_pid,
    blocking.usename     AS blocking_user,
    LEFT(blocking.query, 80) AS blocking_query,
    EXTRACT(EPOCH FROM (NOW() - blocked.query_start))::INT AS wait_sec
FROM pg_stat_activity AS blocked
JOIN pg_stat_activity AS blocking
    ON blocking.pid = ANY(pg_blocking_pids(blocked.pid))
WHERE blocked.wait_event_type = 'Lock'
ORDER BY wait_sec DESC;
pg_blocking_pids() — ブロッキングPIDの確認
-- 特定の PID (例: 12345) をブロックしているセッションを調べる
SELECT pg_blocking_pids(12345);
-- → {67890} のように PID の配列が返る

-- 全セッションのブロッキング関係を展開
SELECT
    pid AS waiting_pid,
    unnest(pg_blocking_pids(pid)) AS blocking_pid
FROM pg_stat_activity
WHERE cardinality(pg_blocking_pids(pid)) > 0;

STEP3:問題セッションを終了する

セッション終了コマンド
-- クエリのみキャンセル(接続は維持、クエリを再実行できる)
SELECT pg_cancel_backend(12345);

-- セッションごと終了(トランザクションはロールバック)
SELECT pg_terminate_backend(12345);

-- ロック待ち中のセッションをまとめて終了
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE wait_event_type = 'Lock'
  AND pid != pg_backend_pid()
  AND EXTRACT(EPOCH FROM (NOW() - query_start)) > 300;  -- 300秒以上待機中

pg_cancel_backend vs pg_terminate_backend:まず pg_cancel_backend() で試してください。クライアントがキャンセルを受け付けない場合のみ pg_terminate_backend() を使います。後者はセッションを強制切断するため、未コミットの変更はすべてロールバックされます。

SQL Server でロックを調査する

STEP1:実行中のリクエストとブロッキングを確認する

sys.dm_exec_requests — 実行中クエリ一覧
-- 現在実行中のリクエスト(SSMS でよく使う基本クエリ)
SELECT
    r.session_id,
    r.blocking_session_id,   -- 0 以外ならブロックされている
    r.wait_type,
    r.wait_time / 1000.0     AS wait_sec,
    r.status,
    r.percent_complete,
    DB_NAME(r.database_id)   AS db_name,
    LEFT(t.text, 200)        AS query_text
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t
WHERE r.session_id != @@SPID  -- 自分自身を除外
ORDER BY r.wait_time DESC;
sp_who2 — シンプルな確認コマンド
-- シンプルにブロッキング状況を確認(SSMS の基本コマンド)
EXEC sp_who2;
-- BlkBy 列が 0 以外のセッションがブロックされている
-- BlkBy に表示された SPID が原因セッション

STEP2:sys.dm_tran_locks でロック詳細を確認する

sys.dm_tran_locks — ロック状態の詳細
-- オブジェクト(テーブル)レベルのロックを確認
SELECT
    l.request_session_id    AS session_id,
    OBJECT_NAME(l.resource_associated_entity_id) AS object_name,
    l.resource_type,
    l.resource_description,
    l.request_mode          AS lock_mode,
    l.request_status        -- GRANT / WAIT / CONVERT
FROM sys.dm_tran_locks l
WHERE l.resource_type = 'OBJECT'
ORDER BY l.request_status, l.object_name;
ブロッキング関係の特定(推奨クエリ)
-- ブロックしているセッションとブロックされているセッションを結合
SELECT
    waiting.session_id      AS waiting_spid,
    waiting.wait_type,
    waiting.wait_time / 1000.0 AS wait_sec,
    waiting_txt.text        AS waiting_query,
    blocking.session_id     AS blocking_spid,
    blocking.status         AS blocking_status,
    blocking_txt.text       AS blocking_query,
    ses.login_name,
    ses.host_name,
    ses.program_name
FROM sys.dm_exec_requests waiting
JOIN sys.dm_exec_sessions  ses     ON ses.session_id = waiting.session_id
JOIN sys.dm_exec_sessions  blocking ON blocking.session_id = waiting.blocking_session_id
CROSS APPLY sys.dm_exec_sql_text(waiting.sql_handle)  waiting_txt
CROSS APPLY sys.dm_exec_sql_text(blocking.most_recent_sql_handle) blocking_txt
WHERE waiting.blocking_session_id > 0
ORDER BY waiting.wait_time DESC;
sys.dm_os_waiting_tasks — 詳細な待機タスク
-- セッション・タスクレベルの待機状況
SELECT
    wt.session_id,
    wt.blocking_session_id,
    wt.wait_type,
    wt.wait_duration_ms / 1000.0 AS wait_sec,
    wt.resource_description
FROM sys.dm_os_waiting_tasks wt
WHERE wt.blocking_session_id IS NOT NULL
ORDER BY wt.wait_duration_ms DESC;

STEP3:問題セッションを KILL する

KILL でセッションを終了
-- 特定の SPID を終了
KILL 52;

-- 複数まとめて確認・実行する場合
SELECT 'KILL ' + CAST(session_id AS VARCHAR) + ';'
FROM sys.dm_exec_requests
WHERE blocking_session_id > 0
  AND wait_time > 60000;   -- 60秒以上待機中
-- 出力を確認してから手動実行

-- WITH STATUSONLY でロールバック進捗を確認
KILL 52 WITH STATUSONLY;
-- → 推定ロールバック残り時間が表示される

KILL WITH STATUSONLY:大きなトランザクションを KILL した直後は、ロールバック処理に時間がかかります。KILL 52 WITH STATUSONLY で進捗を確認しながら待ちましょう。ロールバック中に同じ SPID を再度 KILL しないでください。

Oracle でロックを調査する

Oracle のロック調査は V$LOCKV$SESSIONGV$SESSION を使います。また、セッションの強制切断は ALTER SYSTEM KILL SESSION / ALTER SYSTEM DISCONNECT SESSION で行います。

Oracle — 基本的なロック確認
-- V$LOCK と V$SESSION を結合してロック保持者を特定
SELECT
    s.sid,
    s.serial#,
    s.username,
    s.status,
    s.machine,
    l.type          AS lock_type,
    l.mode_held     AS mode_held,
    l.mode_requested AS mode_req,
    s.sql_id
FROM v$session s
JOIN v$lock l ON l.sid = s.sid
WHERE l.block = 1   -- ブロックしているセッションのみ
ORDER BY s.sid;

-- セッションの強制切断
ALTER SYSTEM KILL SESSION '123,4567' IMMEDIATE;
-- 123 = SID, 4567 = SERIAL#

Oracle のロック調査・セッション切断の詳細(V$SESSION の各列の意味・GV$LOCKでRAC環境の調査・ALTER SYSTEM DISCONNECT SESSION との違いなど)はOracle セッション確認・強制切断完全ガイドを、デッドロック(ORA-00060)の調査・解消についてはOracle デッドロック完全ガイドを参照してください。

ロック調査の手順フロー

ロック発生時に迷わないよう、標準的な調査フローを整理します。

  1. 現象確認:アプリがどのSQL/テーブルで止まっているかを特定
  2. 実行中クエリ確認:SHOW PROCESSLIST / pg_stat_activity / sp_who2 で長時間実行中のセッションを探す
  3. ロック待ちセッション特定:wait_event・blocking_session_id で「誰が誰を待っているか」を確認
  4. ブロッカーの調査:ブロックしているセッションのクエリとトランザクション開始時刻を確認
  5. 解除判断:そのセッションを終了させてよいかビジネス的に判断(処理中のバッチなど影響範囲を確認)
  6. KILL 実行:問題セッションを終了→ロールバックを確認→アプリが回復したか確認
  7. 再発防止:なぜロックが長時間保持されたか原因を分析

よくあるロックの原因パターン

原因 典型的な症状 対処方法
長時間トランザクション トランザクション開始から数分〜数時間 COMMIT/ROLLBACK されていない アプリのトランザクション管理を見直す。自動コミットの確認
自動コミットOFF のままの接続 BEGIN/START TRANSACTION のまま放置 アプリの接続プールでトランザクションのリセットを確認
DDL(ALTER TABLE) Waiting for metadata lock が長時間続く DDL実行前に長時間トランザクションがないか確認してから実行
デッドロック どちらかのトランザクションが自動ロールバックされる テーブルの更新順序を統一する。ロック範囲を最小化する
全テーブルスキャンのUPDATE インデックスなし列でのWHERE UPDATE が膨大な行をロック 適切なインデックスを追加し、ロック範囲を限定する
LOCK TABLES の解除忘れ LOCK TABLES 後に UNLOCK TABLES が実行されていない SHOW OPEN TABLES で確認し UNLOCK TABLES を実行
SHOW OPEN TABLES — テーブルレベルのロックを確認(MySQL)
-- In_use が 1 以上のテーブルが使用中(ロック中)
SHOW OPEN TABLES WHERE In_use > 0;

-- Name_locked = 1 はメタデータロック中
SHOW OPEN TABLES WHERE Name_locked > 0;

-- テーブルレベルロックを解除(LOCK TABLES の場合)
UNLOCK TABLES;

まとめ

ロックの調査・解除は RDBMS ごとに使うビューやコマンドが異なります。

確認内容 MySQL PostgreSQL SQL Server
実行中クエリ SHOW [FULL] PROCESSLIST pg_stat_activity sys.dm_exec_requests / sp_who2
ロック詳細 performance_schema.data_locks pg_locks sys.dm_tran_locks
ブロッキング特定 sys.innodb_lock_waits pg_blocking_pids() blocking_session_id 結合
セッション終了 KILL [id] pg_terminate_backend(pid) KILL [spid]