- MySQL
MySQL SHOW PROCESSLISTの見方とKILLでの切断
SHOW PROCESSLISTとは、MySQLサーバーに今つながっている接続(スレッド)と、それぞれが何をしているかを一覧で見るための文です。 応答が遅い、ALTER TABLEが終わらない、接続数が上限に近い、といった場面で最初に開く画面で、見つけた接続はKILL文で止められます。
この記事では、8つの列の読み方、Command値とState値の意味一覧、FULLの有無、performance_schema.processlistを使ったWHEREでの絞り込み、KILL QUERYとKILL CONNECTIONの違い、KILL文をまとめて作るSQL、wait_timeoutとmax_execution_timeでの予防、権限が足りないときのエラー文までを扱います。掲載したSQLと出力は、すべて下の確認環境で実際に流して取ったものです。
確認環境:Ubuntu 24.04.4 LTS、MySQL 8.0.46(mysqlクライアントも同じ版)。出力のIdは節ごとに取り直しているため、節によって番号が変わります。
SHOW PROCESSLISTとKILLの早見表
| やりたいこと | 書き方 | 補足 |
|---|---|---|
| 接続の一覧を見る | SHOW PROCESSLIST; |
Info列は先頭100文字まで |
| SQL全文を見る | SHOW FULL PROCESSLIST; |
縦表示は末尾を \G にする |
| 条件で絞る・並べ替える | SELECT ... FROM performance_schema.processlist WHERE ... |
8.0.22以降。WHERE、ORDER BY、GROUP BYが使える |
| 実行中の文だけ見る | WHERE COMMAND <> 'Sleep' |
Sleepを除くと一気に見やすくなる |
| N秒以上の文を探す | WHERE COMMAND = 'Query' AND TIME >= 60 |
TIMEは「今の状態になってからの秒数」 |
| 文だけ止める(接続は残す) | KILL QUERY 接続Id; |
相手にはエラー1317が返る |
| 接続ごと切る | KILL 接続Id;(KILL CONNECTION 接続Id; と同じ) |
相手にはエラー2013が返る。開いていたトランザクションも終わる |
| KILL文をまとめて作る | SELECT CONCAT('KILL QUERY ', ID, ';') FROM performance_schema.processlist WHERE ... |
作った文を確認してから流す |
| メタデータロック待ちの元を探す | SELECT ... FROM sys.schema_table_lock_waits |
止める候補のKILL文まで出してくれる |
| 放置された接続を自動で切る | wait_timeout |
既定は28800秒(8時間) |
| 長いSELECTを自動で止める | max_execution_time(ミリ秒) |
読み取り専用のSELECTだけが対象 |
| 他ユーザーの接続を見る権限 | PROCESS |
無ければ自分の接続だけが見える |
| 他ユーザーの接続を止める権限 | CONNECTION_ADMIN(または SUPER) |
無ければエラー1095 |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「MySQL SHOW PROCESSLISTの見方とKILLでの切断」
試すためのデータとユーザーを用意する
以降の出力を手元で再現できるよう、使ったテーブルとユーザーをそのまま載せます。rootで実行します。
CREATE DATABASE shop;
USE shop;
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
member_id INT NOT NULL,
status VARCHAR(20) NOT NULL,
amount INT NOT NULL,
created_at DATETIME NOT NULL
);
SET SESSION cte_max_recursion_depth = 5000;
INSERT INTO orders (member_id, status, amount, created_at)
WITH RECURSIVE s(n) AS (SELECT 1 UNION ALL SELECT n + 1 FROM s WHERE n < 3000)
SELECT n % 100 + 1, ELT(n % 3 + 1, 'new', 'paid', 'shipped'), n * 10, NOW() - INTERVAL n MINUTE
FROM s;
CREATE TABLE order_items AS SELECT * FROM orders;
-- アプリ用(一覧の閲覧権限なし)
CREATE USER 'app'@'%' IDENTIFIED BY 'app_pw';
GRANT SELECT, INSERT, UPDATE, DELETE, ALTER ON shop.* TO 'app'@'%';
-- 集計バッチ用
CREATE USER 'batch'@'%' IDENTIFIED BY 'batch_pw';
GRANT SELECT ON shop.* TO 'batch'@'%';
-- 一覧を見られるだけのユーザー
CREATE USER 'viewer'@'%' IDENTIFIED BY 'viewer_pw';
GRANT PROCESS ON *.* TO 'viewer'@'%';
-- 一覧を見て、止めることもできる運用ユーザー
CREATE USER 'ops'@'%' IDENTIFIED BY 'ops_pw';
GRANT PROCESS, CONNECTION_ADMIN ON *.* TO 'ops'@'%';
ordersとorder_itemsはどちらも3000行です。重いクエリの例には、order_itemsを3回結合する次のSELECTを使います(3000×3000×1000行ぶんの組み合わせを数えるため、放っておくと長時間かかります)。
SELECT /* job-A */ COUNT(*)
FROM order_items a
JOIN order_items b ON a.member_id <> b.member_id
JOIN order_items c ON c.status = a.status;
SHOW PROCESSLISTの出力と8列の読み方
次の5つの接続を同時に作ってから、rootでSHOW PROCESSLISTを実行しました。
- appが接続したまま何もしていない
- appが
BEGIN; SELECT COUNT(*) FROM orders;を実行し、COMMITしないまま止まっている - appが
ALTER TABLE orders ADD COLUMN memo VARCHAR(100)を実行 - appが
SELECT COUNT(*) FROM orders WHERE status='new'を実行 - batchが重いSELECTを2本(job-Aとjob-B)実行
SHOW PROCESSLIST;
+----+-----------------+-----------------+------+---------+------+---------------------------------+--------------------------------------------------
| Id | User | Host | db | Command | Time | State | Info
+----+-----------------+-----------------+------+---------+------+---------------------------------+--------------------------------------------------
| 5 | event_scheduler | localhost | NULL | Daemon | 300 | Waiting on empty queue | NULL
| 40 | app | localhost:41852 | shop | Sleep | 56 | | NULL
| 73 | app | localhost:38736 | shop | Sleep | 10 | | NULL
| 74 | app | localhost:38740 | shop | Query | 7 | Waiting for table metadata lock | ALTER TABLE orders ADD COLUMN memo VARCHAR(100)
| 75 | app | localhost:51534 | shop | Query | 6 | Waiting for table metadata lock | SELECT COUNT(*) FROM orders WHERE status='new'
| 76 | batch | localhost:51550 | shop | Query | 5 | executing | SELECT /* job-A */ COUNT(*) FROM order_items a JO
| 77 | batch | localhost:51560 | shop | Query | 5 | executing | SELECT /* job-B */ COUNT(*) FROM order_items a JO
| 78 | root | localhost | NULL | Query | 0 | init | SHOW PROCESSLIST
+----+-----------------+-----------------+------+---------+------+---------------------------------+--------------------------------------------------
(横に長いため、Info列の右側は途中で切って掲載しています)
1行が1つの接続です。各列は次のように読みます。
| 列 | 意味 | 読むときのポイント |
|---|---|---|
| Id | 接続の番号 | KILLに渡す番号。その接続自身が SELECT CONNECTION_ID() で得る値と同じ |
| User | 接続しているMySQLユーザー | event_scheduler や system user はサーバー内部のスレッド |
| Host | 接続元の「ホスト名:ポート番号」 | どのサーバーやアプリから来た接続かを見分ける手がかり。ソケット接続のrootは localhost だけになる |
| db | 既定のデータベース | 選んでいなければNULL |
| Command | 接続が今実行しているコマンドの種類 | 何もしていなければ Sleep、SQL実行中は Query |
| Time | 今の状態になってからの秒数 | Queryなら実行時間、Sleepなら最後の文が終わってからの時間 |
| State | 処理の中で今どの段階にいるか | Waiting for ... はロック待ち。Sleepのときは空 |
| Info | 実行中の文 | 何も実行していなければNULL。FULLなしでは先頭100文字 |
上の出力は、次のように読めます。
- Id 40と73:CommandがSleepなので、接続したまま次の文を待っています。73は10秒前から止まっています。
- Id 74と75:StateがWaiting for table metadata lockで、テーブル定義のロックを待っています。Timeが待ち時間です。
- Id 76と77:Stateがexecutingで、SELECTを5秒間実行し続けています。
- Id 78:この一覧を出している自分自身です。
- Id 5:イベントスケジューラの内部スレッドで、KILLの対象にはしません。
Timeは「実行時間」とは限らない
Timeは状態が変わるたびに数え直されます。Queryの行では文を始めてからの秒数、Sleepの行では待機に入ってからの秒数です。「Timeが大きい行」を探すときは、必ずCommandとセットで読みます。 Sleepで28000のような値が出ていても、それは長時間放置されている接続という意味で、重いクエリが走っているわけではありません。
FULLを付けるとInfoが全文になる
FULLなしのInfoは先頭100文字までです。長いSQLの後半(WHERE句など)を見たいときはFULLを付けます。縦表示の \G と組み合わせると読みやすくなります。
SHOW FULL PROCESSLIST\G
*************************** 4. row ***************************
Id: 42
User: batch
Host: localhost:35656
db: shop
Command: Query
Time: 6
State: executing
Info: SELECT /* monthly report: aggregate amounts by member for the dashboard (heavy three-way join) */ COUNT(*), SUM(a.amount) FROM order_items a JOIN order_items b ON a.member_id <> b.member_id JOIN order_items c ON c.status = a.status WHERE a.amount > 0
同じ接続をFULLなしで見ると、Infoは次のところで切れていました。
SELECT /* monthly report: aggregate amounts by member for the dashboard (heavy three-way join) */ CO
SQLの先頭にコメントで処理名を書いておくと、一覧から「どの処理の文か」をすぐ見分けられます。 なお、mysqlコマンドラインクライアントは既定でコメントを取り除いて送るため、今回は -c(--comments)オプションを付けて実行しています。
Command値の意味一覧
Commandは「接続が今どの種類のコマンドを処理しているか」です。MySQL 8.0リファレンスマニュアルのThread Command Valuesに載っている値のうち、一覧でよく見るものをまとめます。
| Command | 意味 | 見かける場面 |
|---|---|---|
| Sleep | クライアントから次の文が来るのを待っている | コネクションプールの待機中の接続、開いたままのツール |
| Query | 文を実行している | 通常のSQL実行中。ロック待ちの間もQueryのまま |
| Execute | プリペアドステートメントを実行している | アプリがサーバーサイドのプリペアドステートメントを使う場合 |
| Connect | レプリケーションの受信スレッドやワーカースレッド | レプリカ側 |
| Binlog Dump | ソース側で、バイナリログをレプリカへ送っている | レプリケーションのソース側 |
| Daemon | クライアント接続ではない、サーバー内部のスレッド | event_scheduler など |
| Init DB | 既定のデータベースを選んでいる | USE の実行中 |
| Kill | ほかのスレッドを止めている | KILL文の実行中 |
日々の調査で見分けるのは、ほぼ Sleep(待機)・Query(実行中)・Daemon(内部) の3つです。
State値の意味一覧
Stateは「その文が処理のどの段階にいるか」です。MySQL 8.0リファレンスマニュアルのGeneral Thread Statesに載っている説明をもとに、よく見る値を整理しました。右端の列は、今回の検証で実際に一覧へ出たかどうかです。
| State | 意味 | 長く続くときに見るところ | 今回の出力 |
|---|---|---|---|
| (空) | Sleep中は空になる | Timeと、未コミットのトランザクションの有無 | あり |
| executing | 文の実行を始めている。8.0.17以降は行の読み取りと処理もここに含まれる | 実行計画(EXPLAIN)とインデックス | あり |
| Sending data | 8.0.17より前の表示。SELECTの行を読み、処理し、クライアントへ送っている | 8.0.17以降はexecutingとして表示される | なし(8.0.46ではexecuting) |
| Waiting for table metadata lock | テーブル定義のロック(メタデータロック)を待っている | そのテーブルを使ったまま終わっていないトランザクション | あり |
| Waiting for table flush | テーブルを開き直す必要があり、ほかのスレッドがテーブルを閉じるのを待っている | そのテーブルを長く使い続けている文 | なし |
| User sleep | SLEEP() 関数を呼んでいる |
意図したSLEEPかどうか | あり |
| init | ALTER TABLE、DELETE、INSERT、SELECT、UPDATEの初期化前の段階 | 通常は一瞬 | あり(SHOW PROCESSLIST自身) |
| Opening tables | テーブルを開こうとしている | ALTER TABLEやLOCK TABLEが開くのを止めていないか、table_open_cacheの大きさ | なし |
| statistics | 実行計画を作るための統計を計算している | 長いときはディスクがほかの処理で混んでいる可能性 | なし |
| Creating tmp table | メモリまたはディスクに一時テーブルを作っている | GROUP BYやDISTINCT、一時テーブルの大きさ | なし |
| Creating sort index | 内部の一時テーブルを使って解決するSELECTを処理している | ORDER BYに使えるインデックス | なし |
| Sending to client | クライアントへパケットを書いている | クライアント側の受け取りの遅さ、結果の大きさ | なし |
| update | テーブルの更新を始める準備をしている | 行ロックの待ち | なし |
| Updating | 更新する行を探して更新している | WHERE句に使えるインデックス | なし |
| altering table | インプレースのALTER TABLEを実行している | テーブルの大きさ | なし |
| Killed | KILLが送られ、次にkillフラグを確かめたときに中断する | ほかのスレッドのロックを待っている間は、そのロックが外れてから効く | なし |
| Waiting on empty queue | イベントスケジューラが、実行するイベントが無く待機している | 対応不要 | あり |
「Sending data」は8.0.17以降「executing」になった
以前の版の解説では、重いSELECTのStateは「Sending data」と紹介されています。リファレンスマニュアルには、8.0.17以降はこの状態を個別には表示せず、executingに含めると書かれています。今回の8.0.46でも、3回結合の重いSELECTはずっとexecutingでした。
| 76 | batch | localhost:51550 | shop | Query | 5 | executing | SELECT /* job-A */ COUNT(*) FROM order_items a JO
8.0系で「Sending dataが見つからない」ときは、CommandがQueryでStateがexecuting、かつTimeが大きい行を探します。
よくある3つのパターンを読み解く
パターン1:Sleepがたくさん並んでいる
Sleepは「接続は開いているが、今は何も実行していない」状態です。コネクションプールを使うアプリでは、待機中の接続がSleepで並ぶのは通常の姿です。確認するのは次の2点です。
| 見るところ | 判断 |
|---|---|
| Sleepの数がmax_connectionsに近い | プールの上限の合計が大きい、または接続を閉じていない処理がある |
| SleepなのにトランザクションがCOMMITされていない | ロックを持ったまま止まっている。次のパターン2・3の元になる |
ユーザーとCommandごとに数えると、全体の様子がつかめます。
SELECT USER, COMMAND, COUNT(*) AS cnt, MAX(TIME) AS max_time
FROM performance_schema.processlist
GROUP BY USER, COMMAND
ORDER BY cnt DESC;
+-----------------+---------+-----+----------+
| USER | COMMAND | cnt | max_time |
+-----------------+---------+-----+----------+
| app | Sleep | 2 | 10 |
| batch | Query | 2 | 6 |
| event_scheduler | Daemon | 1 | 254 |
| root | Query | 1 | 0 |
+-----------------+---------+-----+----------+
パターン2:Sleepのままトランザクションを開いている接続を探す
一覧のうえでは、ただのSleepと「BEGINしたまま止まっているSleep」は同じに見えます。見分けるには、InnoDBのトランザクション一覧(information_schema.innodb_trx)と結合します。
SELECT p.ID, p.USER, p.COMMAND, p.TIME AS idle_sec,
t.trx_started,
TIMESTAMPDIFF(SECOND, t.trx_started, NOW()) AS trx_sec
FROM performance_schema.processlist p
JOIN information_schema.innodb_trx t ON t.trx_mysql_thread_id = p.ID
WHERE p.COMMAND = 'Sleep'
ORDER BY trx_sec DESC;
+----+------+---------+----------+---------------------+---------+
| ID | USER | COMMAND | idle_sec | trx_started | trx_sec |
+----+------+---------+----------+---------------------+---------+
| 39 | app | Sleep | 10 | 2026-10-03 18:43:54 | 10 |
+----+------+---------+----------+---------------------+---------+
このとき、appのSleepは2本ありましたが、トランザクションを開いたままなのはId 39だけだとわかります。原因として多いのは、アプリ側でCOMMITやROLLBACKに届く前に処理が抜けている、手作業のツールでBEGINしたまま席を離れている、といったケースです。
パターン3:Waiting for table metadata lockが連なっている
ALTER TABLEなどテーブル定義を変える文は、そのテーブルを使っているトランザクションがすべて終わるまで待ちます。さらに、待っているALTER TABLEの後ろに来たSELECTも、順番待ちで止まります。 最初の出力でId 74(ALTER)とId 75(SELECT)が両方待っていたのはこのためです。
待たせている元は、sysスキーマのschema_table_lock_waitsビューで探せます。
SELECT waiting_pid, LEFT(waiting_query, 40) AS waiting_query,
blocking_pid, sql_kill_blocking_connection
FROM sys.schema_table_lock_waits
WHERE waiting_pid <> blocking_pid;
+-------------+------------------------------------------+--------------+------------------------------+
| waiting_pid | waiting_query | blocking_pid | sql_kill_blocking_connection |
+-------------+------------------------------------------+--------------+------------------------------+
| 74 | ALTER TABLE orders ADD COLUMN memo VARCH | 73 | KILL 73 |
| 75 | SELECT COUNT(*) FROM orders WHERE status | 73 | KILL 73 |
| 75 | SELECT COUNT(*) FROM orders WHERE status | 74 | KILL 74 |
+-------------+------------------------------------------+--------------+------------------------------+
74と75の両方を止めているのがId 73(BEGINしたままSleepしている接続)だと読み取れます。ロックの種類まで見たいときは、performance_schema.metadata_locksを使います。
SELECT OBJECT_NAME, LOCK_TYPE, LOCK_STATUS, t.PROCESSLIST_ID
FROM performance_schema.metadata_locks m
JOIN performance_schema.threads t ON t.THREAD_ID = m.OWNER_THREAD_ID
WHERE OBJECT_SCHEMA = 'shop' AND OBJECT_NAME = 'orders';
+-------------+-------------------+-------------+----------------+
| OBJECT_NAME | LOCK_TYPE | LOCK_STATUS | PROCESSLIST_ID |
+-------------+-------------------+-------------+----------------+
| orders | SHARED_READ | GRANTED | 73 |
| orders | SHARED_UPGRADABLE | GRANTED | 74 |
| orders | EXCLUSIVE | PENDING | 74 |
| orders | SHARED_READ | PENDING | 75 |
+-------------+-------------------+-------------+----------------+
GRANTEDは取得済み、PENDINGは待ちです。73が持つSHARED_READが外れない限り、74のEXCLUSIVEは取れません。
Id 73をKILLしたところ、ALTER TABLEと後続のSELECTはすぐに進みました。
KILL 73;
一覧からは73・74・75の行が消え、待っていたSELECTは件数1000を返し、ordersにはmemo列が追加されていました。
performance_schema.processlistでWHERE絞り込みをする
SHOW PROCESSLISTにはWHERE句を付けられません。条件で絞ったり並べ替えたりしたいときは、テーブルとしてSELECTします。8.0.22以降は performance_schema.processlist が用意されています。列はID、USER、HOST、DB、COMMAND、TIME、STATE、INFOで、SHOW PROCESSLISTと同じ並びです。
SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, LEFT(INFO, 45) AS INFO
FROM performance_schema.processlist
WHERE COMMAND <> 'Sleep'
AND USER NOT IN ('event_scheduler', 'system user')
AND ID <> CONNECTION_ID()
ORDER BY TIME DESC;
+----+-------+-----------------+------+---------+------+-----------+-----------------------------------------------+
| ID | USER | HOST | DB | COMMAND | TIME | STATE | INFO |
+----+-------+-----------------+------+---------+------+-----------+-----------------------------------------------+
| 42 | batch | localhost:35656 | shop | Query | 6 | executing | SELECT /* monthly report: aggregate amounts b |
| 44 | batch | localhost:35670 | shop | Query | 6 | executing | SELECT /* monthly report: aggregate amounts b |
+----+-------+-----------------+------+---------+------+-----------+-----------------------------------------------+
Sleep、内部スレッド、自分自身を除くだけで、「今動いている文」だけが残ります。よく使う条件を並べます。
| 絞り込みたい内容 | WHERE句 |
|---|---|
| 60秒以上実行中の文 | COMMAND = 'Query' AND TIME >= 60 |
| 特定ユーザーの接続 | USER = 'batch' |
| 特定のデータベース | DB = 'shop' |
| 特定の接続元 | HOST LIKE '192.0.2.10:%' |
| ロック待ち | STATE LIKE 'Waiting for%' |
| 特定のテーブルに触れている文 | INFO LIKE '%orders%' |
| 10分以上のSleep | COMMAND = 'Sleep' AND TIME >= 600 |
information_schema.processlistは警告が出る
以前からある information_schema.processlist でも同じ絞り込みができますが、8.0.46では実行のたびに次の警告が付きました。
SELECT ID FROM information_schema.processlist LIMIT 1;
SHOW WARNINGS\G
Level: Warning
Code: 1287
Message: 'INFORMATION_SCHEMA.PROCESSLIST' is deprecated and will be removed in a future release. Please use performance_schema.processlist instead
これから書くスクリプトや監視のSQLは、performance_schema.processlistに揃えておくと、将来の版でもそのまま使えます。
SHOW PROCESSLIST自体をperformance_schema版に切り替える
リファレンスマニュアルによると、従来のSHOW PROCESSLISTは一覧を作る間ミューテックスを取るのに対し、performance_schema版はミューテックスを必要としません。システム変数 performance_schema_show_processlist をONにすると、SHOW PROCESSLISTもこちらの実装で動きます(既定はOFF)。
SET GLOBAL performance_schema_show_processlist = ON;
SHOW PROCESSLIST;
切り替えて実行したところ、列の並びは同じで、自分自身の行のStateだけが従来版のinitからexecutingに変わっていました。
KILL QUERYとKILL CONNECTIONの違い
KILLには2つの形があります。
KILL [CONNECTION | QUERY] 接続Id;
| 書き方 | 止まるもの | 接続 | 相手に返るエラー |
|---|---|---|---|
KILL QUERY 接続Id |
実行中の文だけ | 残る | ERROR 1317 Query execution was interrupted |
KILL CONNECTION 接続Id |
実行中の文と接続 | 切れる | ERROR 2013 Lost connection to MySQL server during query |
KILL 接続Id |
CONNECTIONと同じ | 切れる | 同上 |
接続が切れると、その接続が開いていたトランザクションも終わります。パターン3でBEGINしたままの接続をKILLしたとき、持っていたメタデータロックが外れて後続が進んだのはこのためです。
実行して違いを見る
batchユーザーで、重いSELECT(job-Aとjob-B)を別々の接続から流し、opsユーザーで片方にKILL QUERY、もう片方にKILL CONNECTIONを送りました。どちらの接続も、重いSELECTの8秒後に「次の文」を送るようにしてあります。
-- opsで実行(92がjob-A、93がjob-B)
KILL QUERY 92;
KILL CONNECTION 93;
job-A(KILL QUERYを受けた側)のクライアント出力:
id_before
92
ERROR 1317 (70100) at line 1: Query execution was interrupted
msg id_after
next statement ran 92
job-B(KILL CONNECTIONを受けた側)のクライアント出力:
id_before
93
ERROR 2013 (HY000) at line 1: Lost connection to MySQL server during query
ERROR 2013 (HY000) at line 2: Lost connection to MySQL server during query
KILL QUERYでは接続Id 92が残り、次の文がそのまま実行されました。KILL CONNECTIONでは接続が切れ、次の文もエラーになっています。 KILLの直後に一覧を見ると、92はCommandがSleepの行として残り、93の行は消えていました。
どちらを使うかの判断表
| 状況 | 選ぶもの | 理由 |
|---|---|---|
| 重いSELECTを止めたい | KILL QUERY | 接続を残せるので、コネクションプールやアプリへの影響が小さい |
| BEGINしたままSleepしている接続がロックを持っている | KILL CONNECTION | 実行中の文が無いため、接続を切ってトランザクションを終わらせる |
| Sleepの接続が溜まり、接続数の上限に近い | KILL CONNECTION | 接続そのものを減らす必要がある |
| 大量のUPDATEやDELETEを止めたい | KILL QUERY(時間がかかることを見込む) | 取り消し(ロールバック)にも時間がかかる |
| 内部スレッド(Daemon、system user) | 止めない | レプリケーションやスケジューラの動作に関わる |
KILLしてもすぐには止まらないことがある
リファレンスマニュアルによると、KILLは相手のスレッドに「killフラグ」を立てるだけで、KILL文そのものは確認を待たずに戻ります。相手は処理の区切りでフラグを確かめて中断します。
- SELECTでは、ORDER BYやGROUP BYのループの中で、行のブロックを読むたびに確かめる
- UPDATEやDELETEでは、ブロックを読むたび、また1行更新・削除するたびに確かめる
- ALTER TABLEでは、元のテーブルから数行コピーするごとに確かめる
リファレンスマニュアルには、中断にともなう後始末にも時間がかかると書かれています。大きな更新を止めたあとに行がしばらく残っていても、後始末が終わるのを待ちます。
N秒以上のクエリやSleepからKILL文を作るSQL
止めたい接続が何本もあるときは、SELECTでKILL文を組み立てます。まず対象の一覧を目で見てから、同じ条件でKILL文を作る、という2段階にすると安心です。
5秒以上実行中のbatchユーザーの文を止める
SELECT CONCAT('KILL QUERY ', ID, ';') AS kill_sql, USER, TIME, LEFT(INFO, 30) AS INFO
FROM performance_schema.processlist
WHERE COMMAND = 'Query'
AND TIME >= 5
AND USER = 'batch'
ORDER BY TIME DESC;
+----------------+-------+------+--------------------------------+
| kill_sql | USER | TIME | INFO |
+----------------+-------+------+--------------------------------+
| KILL QUERY 42; | batch | 6 | SELECT /* monthly report: aggr |
| KILL QUERY 44; | batch | 6 | SELECT /* monthly report: aggr |
+----------------+-------+------+--------------------------------+
5秒以上Sleepしている接続を切る
SELECT CONCAT('KILL ', ID, ';') AS kill_sql, USER, TIME
FROM performance_schema.processlist
WHERE COMMAND = 'Sleep'
AND TIME >= 5
AND USER NOT IN ('root', 'event_scheduler', 'system user')
ORDER BY TIME DESC;
+----------+------+------+
| kill_sql | USER | TIME |
+----------+------+------+
| KILL 39; | app | 10 |
| KILL 40; | app | 10 |
+----------+------+------+
検証のため5秒にしていますが、運用では60秒や600秒など、システムに合わせた値にします。
作ったKILL文をまとめて流す
mysqlクライアントの -N(列名を出さない)と -s(罫線を出さない)を付けると、KILL文だけが1行ずつ出ます。
mysql -uroot -p -N -s -e "SELECT CONCAT('KILL QUERY ', ID, ';') FROM performance_schema.processlist WHERE COMMAND = 'Query' AND TIME >= 5 AND USER = 'batch'"
KILL QUERY 42;
KILL QUERY 44;
この出力をファイルに保存して中身を確かめ、問題なければ mysql -uroot -p < kill.sql のように流します。条件には次の3つを入れておくと、巻き込みを防げます。
| 入れておく条件 | 目的 |
|---|---|
USER = '対象ユーザー' または USER NOT IN (...) |
内部スレッドや管理ユーザーを外す |
ID <> CONNECTION_ID() |
自分自身を外す |
COMMAND = 'Query' か COMMAND = 'Sleep' のどちらか |
実行中の文と待機中の接続を分けて扱う |
一覧を見てからKILLを流すまでの間に、対象の文が終わっていることもあります。その場合は次のエラーが出るだけで、ほかへの影響はありません。
ERROR 1094 (HY000) at line 1: Unknown thread id: 99999
権限と、足りないときのエラー文
一覧を見る権限と、止める権限は別です。
| 権限 | できること |
|---|---|
| なし(通常のユーザー) | 自分のユーザーの接続だけ見える。自分のユーザーの接続だけKILLできる |
| PROCESS | 全ユーザーの接続が見える。KILLできるのは自分のユーザーの接続だけ |
| CONNECTION_ADMIN(またはSUPER) | 全ユーザーの接続をKILLできる |
PROCESS権限が無いと、自分の接続だけが見える
ほかにbatchの接続などがある状態で、appユーザーでSHOW PROCESSLISTを実行すると、エラーにはならず、自分の1行だけが返りました。performance_schema.processlistをSELECTした場合も同じで、appには自分の行だけが返りました。「一覧に重いクエリが見当たらない」ときは、まず PROCESS権限を持つユーザーで見ているか を確かめます。
他ユーザーの接続をKILLするとエラー1095
appユーザーからbatchの接続(Id 42)をKILLしようとした結果です。
ERROR 1095 (HY000) at line 1: You are not owner of thread 42
PROCESS権限だけを持つviewerユーザーでは、一覧には全ユーザーの接続(event_scheduler、app、batch、viewer)が出ましたが、KILL QUERYは同じエラーになりました。
ERROR 1095 (HY000) at line 1: You are not owner of thread 42
PROCESSとCONNECTION_ADMINを持つopsユーザーでは、同じKILLが通りました。
エラー文の一覧
| エラー | 出る場面 | 対応 |
|---|---|---|
| ERROR 1095 (HY000): You are not owner of thread N | 他ユーザーの接続をKILLした | CONNECTION_ADMINを持つユーザーで実行する |
| ERROR 1094 (HY000): Unknown thread id: N | そのIdの接続がもう無い | 一覧を取り直す。すでに終わっていれば対応不要 |
| ERROR 1317 (70100): Query execution was interrupted | 自分の文がKILL QUERYで止められた | アプリ側は再実行するかどうかを判断する |
| ERROR 2013 (HY000): Lost connection to MySQL server during query | 文の実行中に接続がKILLされた | 接続し直す |
| ERROR 1142 (42000): SELECT command denied to user ... for table 'schema_table_lock_waits' | sysスキーマのビューを読む権限が無い | sysスキーマへのSELECT権限を持つユーザーで実行する |
最後のエラーは、PROCESSとCONNECTION_ADMINだけを付けたopsユーザーでsys.schema_table_lock_waitsを読んだときに出たものです。sysのビューを調査に使うユーザーには、別途そのための権限が必要です。
wait_timeoutとmax_execution_timeで予防する
毎回手でKILLする代わりに、サーバー側で上限を決めておけます。今回の環境で SHOW GLOBAL VARIABLES を使って確かめた値は次のとおりです(max_connectionsは151でした)。
| 変数 | 単位 | 対象 | 今回の値 |
|---|---|---|---|
| wait_timeout | 秒 | 対話型ではない接続が、何もしないまま待てる時間 | 28800 |
| interactive_timeout | 秒 | 対話型の接続(CLIENT_INTERACTIVEフラグ付き)が待てる時間 | 28800 |
| max_execution_time | ミリ秒 | 読み取り専用のSELECTの実行時間 | 0(上限なし) |
| lock_wait_timeout | 秒 | メタデータロックを待つ時間 | 31536000 |
wait_timeoutでSleepの接続を自動で切る
セッションのwait_timeoutを2秒にして、4秒何もせずに待ってから次の文を送りました。
SET SESSION wait_timeout = 2;
SELECT 'first' AS q;
-- ここで4秒待つ
SELECT 'second' AS q;
q
first
ERROR 4031 (HY000) at line 2: The client was disconnected by the server because of inactivity. See wait_timeout and interactive_timeout for configuring this behavior.
サーバーが接続を閉じ、次の文でエラー4031が返ります。設定するときは、コネクションプールの「接続を使い回す最長時間」をwait_timeoutより短くしておきます。逆にすると、プールが渡した接続がすでに切られていて、アプリ側にこのエラーが出ます。
max_execution_timeで長いSELECTを自動で止める
セッションで1000ミリ秒に設定し、重いSELECTを流しました。
SET SESSION max_execution_time = 1000;
SELECT COUNT(*), SUM(a.amount)
FROM order_items a
JOIN order_items b ON a.member_id <> b.member_id
JOIN order_items c ON c.status = a.status
WHERE a.amount > 0;
ERROR 3024 (HY000) at line 1: Query execution was interrupted, maximum statement execution time exceeded
特定のSELECTだけに上限を付けたいときは、オプティマイザヒントで書けます。500ミリ秒を指定した結果も同じエラーでした。
SELECT /*+ MAX_EXECUTION_TIME(500) */ COUNT(*)
FROM order_items a
JOIN order_items b ON a.member_id <> b.member_id
JOIN order_items c ON c.status = a.status;
ERROR 3024 (HY000) at line 1: Query execution was interrupted, maximum statement execution time exceeded
max_execution_timeはUPDATEには効かない
リファレンスマニュアルには、max_execution_timeは読み取り専用のSELECTだけに適用されると書かれています。500ミリ秒に設定したうえで、1行あたり1ミリ秒待つUPDATEを3000行に対して流すと、最後まで実行されました。
SET SESSION max_execution_time = 500;
SELECT NOW(3) AS started;
UPDATE order_items SET amount = amount + 1 WHERE SLEEP(0.001) = 0;
SELECT ROW_COUNT() AS updated, NOW(3) AS finished;
+-------------------------+
| started |
+-------------------------+
| 2026-10-03 18:44:55.032 |
+-------------------------+
+---------+-------------------------+
| updated | finished |
+---------+-------------------------+
| 3000 | 2026-10-03 18:45:02.443 |
+---------+-------------------------+
約7.4秒かかっていますが、止まっていません。もうひとつ、SELECT SLEEP(2) は上限を超えるとエラーにならず、SLEEPが中断されたことを示す1を返しました。
+-------+
| slept |
+-------+
| 1 |
+-------+
更新系の文には、max_execution_timeとは別の備え(処理を小分けにする、次のlock_wait_timeoutを使う、アプリ側のタイムアウトを設定する)を組み合わせます。
ALTER TABLEの待ち時間はlock_wait_timeoutで区切る
メタデータロックを待つ時間の既定値は31536000秒(365日)で、実質的にはいつまでも待ちます。ALTER TABLEを流す接続だけ短くしておくと、待ちが連なる前に自分から抜けられます。BEGINしたままの接続がある状態で実行した結果です。
SET SESSION lock_wait_timeout = 3;
ALTER TABLE orders ADD COLUMN memo2 VARCHAR(100);
ERROR 1205 (HY000) at line 1: Lock wait timeout exceeded; try restarting transaction
3秒で抜けるため、後ろに並んだSELECTを長く待たせずに済みます。
KILLの前に確かめるチェックリスト
| 確認 | 見るところ |
|---|---|
| PROCESS権限を持つユーザーで見ているか | 一覧に自分の行しか無いときは権限を疑う |
| CommandはQueryかSleepか | Timeの意味が変わる |
| Infoの全文を見たか | SHOW FULL PROCESSLISTか、INFO列をそのままSELECTする |
| 止める対象は読み取りか更新か | 更新は取り消しに時間がかかる |
| ロック待ちなら、待たせている元を特定したか | sys.schema_table_lock_waits、innodb_trxとの結合 |
| KILL QUERYで足りるか | 接続を残せるなら、まずQUERYを選ぶ |
| 内部スレッドや自分自身を対象から外したか | USERの条件と ID <> CONNECTION_ID() |
| 止めたあと、同じ文がまた来ないか | アプリの再試行やバッチの再実行の有無 |
KILLはその場をしのぐ手当てです。同じ文が繰り返し長時間かかるなら、Infoで取れたSQLをEXPLAINにかけ、インデックスや書き方を見直すところまで進めると、次からは一覧に現れなくなります。
つまずきやすい点と直し方
| 起きること | 理由 | こうするとよい |
|---|---|---|
| 一覧に自分の接続しか出ない | PROCESS権限が無い | PROCESSを持つユーザーで見る |
| Sending dataの行が見つからない | 8.0.17以降はexecutingに含まれる | CommandがQueryでStateがexecutingの行をTime順に見る |
| Infoが途中で切れている | FULLなしは100文字まで | SHOW FULL PROCESSLISTを使う |
| ALTER TABLEを止めても後続が進まない | 元になっているトランザクションが残っている | schema_table_lock_waitsでblocking_pidを確かめる |
| KILLしたのに行が残る | 変更の取り消し中 | 終わるまで待つ |
| max_execution_timeを設定したのにUPDATEが止まらない | 対象は読み取り専用のSELECTだけ | 更新は小分けにし、アプリ側のタイムアウトも使う |
よくある質問
Q. SHOW PROCESSLISTの「プロセス」は、OSのプロセスのことですか?
A. いいえ。MySQLサーバーの中の接続(スレッド)のことです。IdはMySQLが振る接続番号で、OSのプロセスIDとは別物です。OSの kill コマンドではなく、MySQLのKILL文に渡します。
Q. KILLとKILL CONNECTIONは同じですか? A. 同じです。CONNECTIONもQUERYも付けないKILLは、KILL CONNECTIONとして動きます。接続を残したいときは、明示的にKILL QUERYと書きます。
Q. Sleepの接続は、見つけたら全部切ったほうがよいですか? A. コネクションプールの待機中の接続はSleepで並ぶのが通常の姿なので、すべてを切る必要はありません。切る対象は、トランザクションを開いたまま止まっている接続や、接続数の上限を押し上げている長時間のSleepです。innodb_trxとの結合で前者を見分けられます。
Q. information_schema.processlistとperformance_schema.processlistは、どちらを使えばよいですか? A. 8.0.22以降ではperformance_schema.processlistをおすすめします。8.0.46ではinformation_schema.processlistを読むたびに、将来の版で削除されるという警告(1287)が出ました。列の並びは同じなので、テーブル名を変えるだけで移れます。
まとめ
- SHOW PROCESSLISTは1行が1接続。 Id・User・Host・db・Command・Time・State・Infoの8列で、「誰が、どこから、何を、どれくらい」を読む
- TimeはCommandとセットで読む。 Queryなら実行時間、Sleepなら待機時間
- 8.0.17以降、重いSELECTのStateはexecuting。 Sending dataは個別には表示されない
- 絞り込みはperformance_schema.processlist。 WHERE、ORDER BY、GROUP BYが使え、innodb_trxやsysのビューと結合すれば、ロックを持ったSleepやメタデータロック待ちの元まで追える
- KILL QUERYは文だけ、KILL CONNECTIONは接続ごと。 相手にはそれぞれエラー1317、2013が返る
- 見るにはPROCESS、他ユーザーを止めるにはCONNECTION_ADMIN。 足りないとエラー1095になる
- 予防はwait_timeout(放置された接続)、max_execution_time(読み取りのSELECT)、lock_wait_timeout(メタデータロック待ち) の3つを使い分ける
参考・出典
- MySQL 8.0 Reference Manual「SHOW PROCESSLIST Statement」 https://dev.mysql.com/doc/refman/8.0/en/show-processlist.html
- MySQL 8.0 Reference Manual「KILL Statement」 https://dev.mysql.com/doc/refman/8.0/en/kill.html
- MySQL 8.0 Reference Manual「Thread Command Values」 https://dev.mysql.com/doc/refman/8.0/en/thread-commands.html
- MySQL 8.0 Reference Manual「General Thread States」 https://dev.mysql.com/doc/refman/8.0/en/general-thread-states.html
- MySQL 8.0 Reference Manual「Server System Variables」 https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「MySQL SHOW PROCESSLISTの見方とKILLでの切断」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。