MySQL8 にて Error 1040: Too many connections になった時の対応

Error 1040: Too many connections というエラーが起きてDBにアクセスできなくなった。
これはそのままの意味で DB の接続数が多すぎるといったエラー。
運用しているサービスが一時利用不能&不安定な状態になってしまった。

環境

  • CentOS7
  • MySQL8
  • Golang (バックエンド)

主な対処方法

  • DB の最大接続数を増やす
  • トランザクション処理を見直す ← 自分はこれだった

現状の接続数などの確認

Threads_connected がコマンドを叩いた時点の接続数。

$ mysqladmin -u username -p extended-status | egrep '(Max|Threads_)';

| Max_execution_time_exceeded   | 0   |
| Max_execution_time_set        | 0   |
| Max_execution_time_set_failed | 0   |
| Max_used_connections          | 274 |
| Max_used_connections_time     | 2020-12-01 00:00:00 |
| Threads_cached                | 96  |
| Threads_connected             | 3   |
| Threads_created               | 335 |
| Threads_running               | 2   |

最大接続数の確認

デフォルトで 151 らしい。

# MySQL へ接続
$ mysql -u username -p

# 最大接続数を調べる
mysql> show variables like 'max_connections';

+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| max_connections | 151   |
+-----------------+-------+

そもそも接続数とは

ざっくり調べても見つからなかったので憶測だけど、
API でいうところのユーザーが何かのエンドポイントにアクセスして DB の接続処理を行って、
全ての処理が終わってレスポンスを返すところくらいまでが 1 接続だと思われる。
SQL の発行数とかではない。
やらないと思うけど処理の中で何度も DB.Connect(...) みたいな処理を挟んでいると、
接続数としてカウントされるのではないかと思う。

リクエストからレスポンスまでほとんど 1 秒もかからないから、
151 接続でもけっこうなアクセスになると思う。

DB の最大接続数を増やす

最大接続数をどれだけ増やすのがいいのか、
色々ブログを漁ったけどほとんどが明確な理由がなく適当に増やしていたので、
適当に増やす。

$ vi /etc/my.cnf

/etc/my.cnf

[mysqld]

# 下記追加
# 接続数を 10000 にする
max_connections = 10000
# 再起動
$ systemctl restart mysqld.service

トランザクション処理を見直す

自分は Go + Gin + GORM でトランザクション処理をしていたところが原因だった。

Transactions by manual - GORM

トランザクションを開始して、
全ての処理が終わった時に Rollback() もしくは Commit() をしないと、
トランザクション処理が終わらず、ずっと接続された状態が続く。
これがたまりにたまって Too many connections になっていた。

// トランザクションを開始
tx := db.Begin()

// エラーの場合にロールバック
tx.Rollback()

// 成功の場合にコミット
tx.Commit()

その他詳細を調べたい時

INNODB_TRX テーブル

# MySQL へ接続
$ mysql -u username -p

# プロセスリスト
# 接続数と内容をざっくり調べる
mysql> show processlist;

+------+-------+-----------------+---------------+---------+------+----------+------------------+
| Id   | User  | Host            | db            | Command | Time | State    | Info             |
+------+-------+-----------------+---------------+---------+------+----------+------------------+
| 9779 | test  | localhost:12000 | dbname        | Sleep   |    9 |          | NULL             |
| 9780 | test  | localhost:13000 | dbname        | Sleep   |    4 |          | NULL             |
| 9781 | test  | localhost       | dbname        | Query   |    0 | starting | show processlist |
+------+-------+-----------------+---------------+---------+------+----------+------------------+


# トランザクションでロックがかかっている件数やクエリなど
mysql> SELECT trx_rows_locked, TRX_STATE, TRX_STARTED, TRX_QUERY FROM information_schema.INNODB_TRX;

# トランザクション数
mysql> SELECT count(trx_id) FROM information_schema.INNODB_TRX;