MySQL に新しく接続しようとすると、次のエラーで弾かれることがあります。
ERROR 1040 (08004): Too many connections
同時接続数が max_connections(既定 151)に達したときのエラーです。この記事のログは MySQL 8.4.6 で採りました。再現しやすくするため、max_connections を 5 に下げて試しています。
まず見る(結論)
- アプリのユーザーが弾かれていても、
CONNECTION_ADMIN権限を持つユーザー(root など)は1本だけ追加で入れる - 入れたら
SHOW PROCESSLISTで、何が接続を埋めているかを見る - 不要な接続は
KILLで切る。急ぎならSET GLOBAL max_connectionsで上限を上げる(再起動は要らない)
この1本の枠は、root で入りっぱなしにしているとそれで埋まります。監視や手作業で root 接続を張ったままにしないことが前提です。
何本目で弾かれるか
SET GLOBAL max_connections = 5; にして、CONNECTION_ADMIN などの権限を持たない一般ユーザー app で接続を1本ずつ張りました。5本目までは通り、6本目が冒頭の ERROR 1040 (08004) になりました。
その状態で root から接続すると、1本目は通りました。2本目は次のエラーです。
ERROR 1040 (HY000): Too many connections
同じ 1040 でも、一般ユーザーが弾かれたときの SQLSTATE は 08004、root の2本目は HY000 でした。
入れたら接続を埋めている相手を見る
上限に達した状態で、root から見た結果です。
SHOW GLOBAL STATUS LIKE 'Threads_connected';
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| Threads_connected | 6 |
+-------------------+-------+
SHOW PROCESSLIST;
+-----+-----------------+-----------+------+---------+-------+------------------------+---------------------------------+
| Id | User | Host | db | Command | Time | State | Info |
+-----+-----------------+-----------+------+---------+-------+------------------------+---------------------------------+
| 5 | event_scheduler | localhost | NULL | Daemon | 11822 | Waiting on empty queue | NULL |
| 363 | app | localhost | NULL | Query | 10 | User sleep | SELECT "app_conn_1", SLEEP(180) |
| 365 | app | localhost | NULL | Query | 9 | User sleep | SELECT "app_conn_2", SLEEP(180) |
| 367 | app | localhost | NULL | Query | 8 | User sleep | SELECT "app_conn_3", SLEEP(180) |
| 369 | app | localhost | NULL | Query | 6 | User sleep | SELECT "app_conn_4", SLEEP(180) |
| 371 | app | localhost | NULL | Query | 5 | User sleep | SELECT "app_conn_5", SLEEP(180) |
| 376 | root | localhost | NULL | Query | 0 | init | SHOW PROCESSLIST |
+-----+-----------------+-----------+------+---------+-------+------------------------+---------------------------------+
Threads_connected は app の5本と root の1本で 6 です。event_scheduler は一覧に出ますが、数には入っていません。
実際の障害では、Command が Sleep の接続が大量に並ぶことが多いです。User と Host で、どのアプリのどのサーバーから来た接続かを数えます。
SELECT user, SUBSTRING_INDEX(host, ':', 1) AS client, command, COUNT(*)
FROM performance_schema.processlist
GROUP BY user, client, command
ORDER BY COUNT(*) DESC;
MySQL 8.4 では information_schema.processlist でも同じ結果になりますが、'INFORMATION_SCHEMA.PROCESSLIST' is deprecated の警告(1287)が出ます。
あるアプリだけが極端に多ければ、そのアプリのコネクションプールの上限か、接続を閉じていない箇所を疑います。
弾かれた回数を見る
Connection_errors_max_connections は、1040 で弾かれた回数の累計です。検証では、app の6本目と root の2本目の2回で 22 から 24 に増えました。
SHOW GLOBAL STATUS LIKE 'Connection_errors_max_connections';
SHOW GLOBAL STATUS LIKE 'Max_used_connections%';
Max_used_connections は起動してからの同時接続数の最大値で、Max_used_connections_time がその時刻です。上限にどれだけ近づいたことがあるかを、障害が起きる前に確かめられます。
1040 はエラーログには出ませんでした。弾かれたかどうかは、アプリ側のログか上の累計で確かめます。
空ける(KILL)・上限を上げる
上限に達した状態で root から app の接続を1本 KILL すると、直後に app の新しい接続が通りました。
KILL <Id>;
上限は SET GLOBAL max_connections = 300; のように変えられます。この変更は再起動で元に戻るので、残すなら設定ファイルにも書くか SET PERSIST を使います。上限を上げるとその分メモリを使います。増え続ける原因を止めずに上げるだけでは、また埋まります。
アイドル接続を自動で切る(wait_timeout)
何もしていない接続は、wait_timeout(既定 28800 秒=8時間)を過ぎるとサーバーが切ります。
SET GLOBAL wait_timeout = 10; にしてから新しく接続し、何もせずに15秒待つと、その接続は SHOW PROCESSLIST から消えていました。切られたクライアントが次に SQL を投げたときのエラーです。
ERROR 4031 (HY000): The client was disconnected by the server because of inactivity. See wait_timeout and interactive_timeout for configuring this behavior.
SET GLOBAL wait_timeout が効くのは、変更した後に作られた接続です。mysql コマンドを対話モードで使う接続には interactive_timeout が使われます。短くしすぎると、プールに置かれた接続がアプリの知らない間に切られて 4031 になります。アプリのプール側のアイドル時間より長くしておきます。
ユーザーごとに上限を付ける
1つのアプリが全体の枠を使い切らないように、ユーザー単位で上限を付けられます。
ALTER USER app WITH MAX_USER_CONNECTIONS 2;
max_connections を 151 に戻した状態で、app の3本目は次のエラーになりました。
ERROR 1226 (42000): User 'app' has exceeded the 'max_user_connections' resource (current value: 2)
エラー番号は 1040 ではなく 1226 です。他のユーザーは引き続き接続できるので、どのアプリが原因かがエラーで分かります。
管理用ポートは後から有効にできない
MySQL 8.0.14 以降には、通常の接続と別に管理用ポート(admin_port、既定 33062)があります。admin_address を設定すると有効になり、max_connections の数に関係なく管理者が入れます。検証環境では未設定(NULL)で、SET GLOBAL admin_address = '127.0.0.1'; は次のエラーでした。
ERROR 1238 (HY000): Variable 'admin_address' is a read only variable
設定ファイルに書いて再起動しないと使えません。接続が埋まってから設定しても間に合わないので、使うなら平常時に設定しておきます。