MySQL 12 min read

MySQLの「Too many connections」 ― 接続が埋まったときにrootで入って空ける

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

設定ファイルに書いて再起動しないと使えません。接続が埋まってから設定しても間に合わないので、使うなら平常時に設定しておきます。