「クエリが応答しない」「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 列が大きいものや State が Waiting for lock になっているものが怪しいセッションです。
-- 現在のセッション一覧(クエリが長い場合は短縮表示)
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_locks と data_lock_waits が推奨です(旧バージョンは information_schema.innodb_locks を使用)。
-- 現在アクティブな 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;
-- どのトランザクションがどの行をロックしているか
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 スキーマを使った一発確認クエリ(管理者向けに最適化済み)
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_LOCKS と INNODB_LOCK_WAITS を使います。MySQL 8.0 でこれらは削除されました。
-- 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 する
-- 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:実行中クエリとロック待ちを確認する
-- 現在アクティブなセッション(アイドルを除く)
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 でロックの詳細を確認する
-- ロック一覧(テーブル名付き)
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;
-- 特定の 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:実行中のリクエストとブロッキングを確認する
-- 現在実行中のリクエスト(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;
-- シンプルにブロッキング状況を確認(SSMS の基本コマンド) EXEC sp_who2; -- BlkBy 列が 0 以外のセッションがブロックされている -- BlkBy に表示された SPID が原因セッション
STEP2: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;
-- セッション・タスクレベルの待機状況
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 する
-- 特定の 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$LOCK・V$SESSION・GV$SESSION を使います。また、セッションの強制切断は ALTER SYSTEM KILL SESSION / ALTER SYSTEM DISCONNECT SESSION で行います。
-- 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 デッドロック完全ガイドを参照してください。
ロック調査の手順フロー
ロック発生時に迷わないよう、標準的な調査フローを整理します。
- 現象確認:アプリがどのSQL/テーブルで止まっているかを特定
- 実行中クエリ確認:SHOW PROCESSLIST / pg_stat_activity / sp_who2 で長時間実行中のセッションを探す
- ロック待ちセッション特定:wait_event・blocking_session_id で「誰が誰を待っているか」を確認
- ブロッカーの調査:ブロックしているセッションのクエリとトランザクション開始時刻を確認
- 解除判断:そのセッションを終了させてよいかビジネス的に判断(処理中のバッチなど影響範囲を確認)
- KILL 実行:問題セッションを終了→ロールバックを確認→アプリが回復したか確認
- 再発防止:なぜロックが長時間保持されたか原因を分析
よくあるロックの原因パターン
| 原因 | 典型的な症状 | 対処方法 |
|---|---|---|
| 長時間トランザクション | トランザクション開始から数分〜数時間 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 を実行 |
-- 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] |
- まず「誰が誰をブロックしているか」を特定する —
sys.innodb_lock_waits/pg_blocking_pids()/blocking_session_idが便利 - KILL は影響範囲を確認してから — トランザクションはロールバックされる
- ロックの「仕組みと設計」はテーブル・行ロック完全ガイドを参照
- Oracle の詳細調査はOracle セッション確認・強制切断完全ガイドを参照
