2026年8月12日

2026年8月12日

MySQLのコネクションプールをPHP・WordPressで実現する方法(永続接続・ProxySQL・max_connections)

PHPには、JavaやNode.jsのような「アプリ内で接続を共有するコネクションプール」がありません。PHP・WordPress環境で接続数の問題を解決するには、①PHP-FPMのプロセス数で同時接続数の上限を決める、②MySQLの max_connections をその上限に合わせる、③それでも足りない場合にProxySQLのような外部プロキシで接続をまとめる、という順で考えるのが現実的です。この記事では、なぜPHPにプールがないのかという仕組みから、永続接続の注意点、ProxySQLの導入手順、効果の確認方法までを説明します(2026年10月現在)。

なぜPHPには組み込みのコネクションプールがないのか

PHPは「1リクエストごとに処理を始め、終わったら状態を捨てる」設計です。PHP-FPMでは複数のワーカープロセスが並んで待機し、1つのプロセスが同時に処理するリクエストは1件だけです。プロセス同士はメモリを共有しないため、あるプロセスが開いたMySQL接続を別のプロセスが借りることはできません。

WordPressの $wpdb も、リクエストの最初に mysqli で接続し、リクエストが終わると接続が閉じられます。つまり 「同時に動いているPHPプロセスの数 ≒ MySQLへの同時接続数」 になります。Javaなどのプールが解決している「接続の作成コスト」は、MySQLがローカルやLAN内にある場合はもともと小さく、PHP環境で実際に問題になるのは、ほとんどの場合「接続数の上限超過」のほうです。

接続数が上限に達すると、WordPressは「データベース接続確立エラー」を表示し、MySQL側では Too many connections(エラー1040)が記録されます。

現状の接続数を確認する

対策の前に、どれだけ接続が使われているかを確認します。

-- 上限値
SHOW VARIABLES LIKE 'max_connections';

-- 現在の接続数と、起動以来の最大同時接続数
SHOW GLOBAL STATUS LIKE 'Threads_connected';
SHOW GLOBAL STATUS LIKE 'Max_used_connections';

-- 上限超過で拒否された接続の累計
SHOW GLOBAL STATUS LIKE 'Connection_errors_max_connections';

-- どのユーザー・ホストから何本つながっているか
SELECT user, host, command, COUNT(*) AS cnt
FROM information_schema.processlist
GROUP BY user, host, command
ORDER BY cnt DESC;

Max_used_connections が max_connections に近い、または Connection_errors_max_connections が増えているなら、上限に当たっています。processlist で Sleep 状態の接続が大量にある場合は、後述の永続接続やタイムアウト設定を疑います。

PHP-FPMのプロセス数が接続数の上限を決める

PHP-FPMのプール設定にある pm.max_children は、そのプールで同時に動けるPHPプロセスの最大数です。WordPressは通常1リクエストにつき1本の接続を使うため、ここがMySQLへの同時接続数の実質的な上限になります。

; /etc/php/8.3/fpm/pool.d/www.conf の例
pm = dynamic
pm.max_children = 20
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 8
pm.max_requests = 500

1台のサーバーで複数サイトをそれぞれ別のプールで動かしている場合は、各プールの max_children を合計します。たとえば3サイト×20なら最大60本です。これにWP-Cronの実行、管理用のmysqlクライアント、バックアップスクリプトなどの分を足したものが、MySQLに必要な接続数の目安になります。

ここで大事なのは、**接続数が足りないときに max_connections だけを上げるのではなく、max_children が適切かを先に見直す**ことです。CPUコア数に対してPHPプロセスが多すぎると、MySQLに大量の接続が同時に押し寄せ、どのクエリも遅くなります。PHP-FPM側でリクエストを待たせたほうが、全体の応答はかえって安定することが多いです。max_children の目安は、空きメモリをPHPプロセス1つあたりの使用メモリで割った値と、CPUコア数の両方から決めます。

max_connectionsを適切な値にする

max_children の合計から必要数が決まったら、MySQLの上限をそれに合わせます。MySQL 8.0 の既定値は151です。

# /etc/mysql/mysql.conf.d/mysqld.cnf(ディストリビューションによりパスは異なる)
[mysqld]
max_connections = 200
wait_timeout = 600
thread_cache_size = 32

設定ファイルを変更する前に、元のファイルをコピーしておいてください。反映には sudo systemctl restart mysql が必要です。再起動せずに試したい場合は SET GLOBAL max_connections = 200; で即時変更できますが、再起動すると元に戻ります(MySQL 8.0 では SET PERSIST で永続化もできます)。

注意点は次のとおりです。

  • メモリ:接続1本ごとに、ソートや結合用のバッファがスレッド単位で確保されます。上限を大きくしすぎると、ピーク時にメモリが足りなくなり、OSにMySQLごと停止されるおそれがあります。
  • wait_timeout:アイドル状態の接続を切るまでの秒数で、既定は28800秒(8時間)です。永続接続などで Sleep の接続がたまる場合は短くします。
  • thread_cache_size:切断されたスレッドを再利用のために残しておく数です。SHOW GLOBAL STATUS LIKE 'Threads_created'; の値が接続数(Connections)に対して大きく増え続けるなら、少し増やします。

永続接続はプールの代わりになるか

PHPには「永続接続」という機能があります。mysqli ではホスト名の前に p: を付け、PDO では PDO::ATTR_PERSISTENT を指定すると、リクエストが終わっても接続を閉じずにプロセス内に残し、次のリクエストで再利用します。

<?php
// mysqli の永続接続
$mysqli = new mysqli( 'p:127.0.0.1', 'app_user', 'your-db-password', 'app_db' );

// PDO の永続接続
$pdo = new PDO(
    'mysql:host=127.0.0.1;dbname=app_db;charset=utf8mb4',
    'app_user',
    'your-db-password',
    array( PDO::ATTR_PERSISTENT => true )
);

これで接続の作成コストは省けますが、接続数は減りません。接続はプロセスごとに保持されるので、max_children が20なら、リクエストがないときも最大20本の接続が Sleep のまま残り続けます。さらに、前のリクエストで設定したセッション変数やロック、未完了のトランザクションが次のリクエストに持ち越されるリスクがあります(mysqli は再利用時に接続状態のリセットを行いますが、PDOの永続接続ではアプリ側で注意が必要です)。

WordPressのコアには永続接続を有効にする公式の設定はありません。プラグインやドロップインで無理に有効にするより、次に説明するプロキシで接続をまとめるほうが、効果も分かりやすく安全です。

ProxySQLで接続をまとめる

ProxySQLは、アプリとMySQLの間に置くMySQL互換のプロキシです。アプリからの接続はProxySQLが受け、MySQLとの間では自分が保持している接続(バックエンド接続)を使い回します。これを「多重化(マルチプレクシング)」と呼び、PHP側の接続が100本あっても、MySQL側は数十本で済む、といった構成が可能になります。DBサーバーを複数のWebサーバーで共有している場合に特に効果があります。

ただし、トランザクション中、一時テーブルを作った接続、セッション変数を変更した接続などは、安全のため特定のバックエンド接続に固定され、多重化されません。効果はアプリのクエリの使い方によって変わります。

以下は、WebサーバーにProxySQLを入れ、別ホストのMySQLへ中継する最小構成の例です。ProxySQLは既定で、管理用インターフェースをポート6032、アプリ用をポート6033で待ち受けます。

-- 管理インターフェースに接続: mysql -u admin -p -h 127.0.0.1 -P 6032
-- (初期の管理パスワードは導入直後に変更しておく)

-- 中継先のMySQLを登録。max_connections はこのバックエンドへの接続上限
INSERT INTO mysql_servers (hostgroup_id, hostname, port, max_connections)
VALUES (10, '10.0.0.5', 3306, 50);

-- アプリが使うユーザーを登録(MySQL側にも同じユーザーが必要)
INSERT INTO mysql_users (username, password, default_hostgroup)
VALUES ('wp_user', 'your-db-password', 10);

-- 死活監視用ユーザー(MySQL側に作成しておく)
UPDATE global_variables SET variable_value = 'monitor'
WHERE variable_name = 'mysql-monitor_username';
UPDATE global_variables SET variable_value = 'your-monitor-password'
WHERE variable_name = 'mysql-monitor_password';

-- 設定を反映し、ディスクにも保存
LOAD MYSQL SERVERS TO RUNTIME; SAVE MYSQL SERVERS TO DISK;
LOAD MYSQL USERS TO RUNTIME;   SAVE MYSQL USERS TO DISK;
LOAD MYSQL VARIABLES TO RUNTIME; SAVE MYSQL VARIABLES TO DISK;

ProxySQLの設定は「メモリ上の設定 → RUNTIME(実際に使われる設定)→ DISK(再起動後も残る設定)」の3層になっています。INSERT しただけでは反映されず、LOAD ... TO RUNTIME を忘れると「設定したのに動かない」状態になります。SAVE ... TO DISK を忘れると、再起動で設定が消えます。

WordPress側は、wp-config.php の接続先をProxySQLに変えるだけです。変更前に wp-config.php のバックアップを取っておいてください。

define( 'DB_HOST', '127.0.0.1:6033' );

接続できない場合は、MySQL 8.0以降の既定の認証方式(caching_sha2_password)とProxySQLのバージョンの対応状況が原因のことがあります。使用しているProxySQLのドキュメントで認証方式の対応を確認してください。

反映後は、管理インターフェースでバックエンド接続の状況を確認できます。

SELECT hostgroup, srv_host, status, ConnUsed, ConnFree, ConnOK, ConnERR, Queries
FROM stats_mysql_connection_pool;

ConnUsed と ConnFree の合計がMySQL側の接続数です。PHP側のプロセス数より十分少なく、ConnERR が増えていなければ、多重化が働いています。

どの対策から始めるか

規模別の目安は次のとおりです。

  • Webサーバー1台・MySQL同居:max_children を適正化し、その合計に余裕を持たせて max_connections を設定するだけで足りることがほとんどです。
  • Webサーバー複数台・DB共有:台数×max_children が大きくなるため、各WebサーバーにProxySQLを置くか、DBの手前にまとめて置く構成を検討します。
  • AWSのRDS/Aurora:マネージドのRDS Proxyという選択肢があります。

どの場合も、先に遅いクエリを直すと接続の占有時間が短くなり、必要な接続数そのものが減ります。スロークエリログで時間のかかっているクエリを確認するのも、接続数対策の一部です。

まとめ

  • PHPはプロセス間で接続を共有しないため、組み込みのコネクションプールはない
  • 同時接続数の上限は、PHP-FPMの pm.max_children の合計でほぼ決まる。まずここを適正化する
  • max_connections は必要数に余裕を持たせて設定し、メモリ使用量と Connection_errors_max_connections で確認する
  • 永続接続は接続作成コストを省くだけで、接続数は減らない。状態の持ち越しにも注意が必要
  • 接続数そのものを減らしたい場合は、ProxySQLの多重化を使う。設定は LOAD ... TO RUNTIME と SAVE ... TO DISK で反映する

お気軽にご相談ください

お見積りへ お問い合わせへ
▲