PHP PDOにおける「2006 MySQL Server Has Gone Away」エラーの解決方法

intermediate🐘 PHP2026-05-28| PHP 7.4/8.x, MySQL 5.7/8.0, MariaDB, Linux (Ubuntu/CentOS), Docker

Error Message

PDOException: SQLSTATE[HY000]: General error: 2006 MySQL server has gone away
#php#pdo#mysql#データベース#接続#タイムアウト

問題:突然の切断深夜2時に数千個の在庫アイテムを同期するcronジョブを実行している最中、プロセスが突然停止したとします。ログを確認すると、悪名高い 2006 MySQL server has gone away エラーが発生しています。データベース自体は稼働しており、ターミナルからは問題なくログインできるため、不可解に感じるかもしれません。要するに、MySQLサーバーがスクリプトに対して「ドアを閉めた」状態ですが、PHPコードはまだ期限切れの接続ハンドルを使用しようとしているのです。

なぜサーバーは切断するのか?MySQLが接続を終了する主な理由は3つあります。まず、クエリが大きすぎる場合です。もし max_allowed_packet の制限が16MBのサーバーに対して50MBのPDFを送信しようとすると、サーバーは即座にリンクを切断します。

次に、データベース呼び出しの間にスクリプトの処理が遅すぎる場合です。次のクエリを発行する前にPHPコードがデータの処理に20分かかってしまうと、MySQLの wait_timeout が期限切れになり、アイドル状態の接続が切断されます。最後に、高いRAM使用率により、LinuxのOut of Memory (OOM) killerによってMySQLサービス自体が再起動または終了させられた可能性もあります。

設定による修正:エンジンのチューニング大量のデータインポートを処理する最も手っ取り早い方法は、my.cnf または my.ini ファイルでパケットサイズとタイムアウト制限を増やすことです。これらの設定によって、サーバーが許容する最大データサイズとアイドル時間が決まります。

1. max_allowed_packet の増加1つのステートメントで5,000行を挿入するような一括インサートを行う場合、デフォルトの16MB制限では不十分なことがよくあります。余裕を持たせるために、これを128MBまたは256MBに引き上げましょう。

# /etc/mysql/my.cnf または /etc/my.cnf に配置
[mysqld]
max_allowed_packet = 128M

2. wait_timeout の延長データベース更新の間に重い計算を行うバックグラウンドワーカーの場合は、アイドルタイムアウトを増やします。デフォルトは通常28,800秒(8時間)ですが、AWS RDSのようなマネージド環境では、これが大幅に低く設定されている場合があります。

[mysqld]
wait_timeout = 28800
interactive_timeout = 28800

これらの変更を保存した後、サービスを再起動して適用します。

sudo systemctl restart mysql

恒久的な修正:PHPの再接続ロジック設定変更は有効ですが、万全ではありません。不安定なネットワークや長時間実行されるタスクでは、接続がいずれ切断されることを想定しておく必要があります。PHPのPDOは自動的に再接続しないため、切断されたリンクを検出し、復元するためのラッパーが必要です。

「Ping」メソッドループ内で重要なクエリを実行する前に、接続がまだ生きているか確認します。これは、軽量な SELECT 1 クエリを実行することで確認できます。もし2006エラーコードで失敗した場合は、例外をキャッチして新しいPDOオブジェクトをインスタンス化します。

/**
 * 処理を続行する前にPDO接続がアクティブであることを確認する
 */
function getActiveConnection($pdo, $dsn, $user, $pass, $options) {
    try {
        $pdo->query('SELECT 1');
    } catch (PDOException $e) {
        // 「Gone Away」エラーを特定してチェック
        if (strpos($e->getMessage(), '2006') !== false) {
            return new PDO($dsn, $user, $pass, $options);
        }
        throw $e;
    }
    return $pdo;
}

バッチ処理のハンドリング大規模なデータセットを反復処理する場合、ステップ間に長い遅延があるなら、古い接続を開いたままにしないでください。実行部分をtry-catchブロックで囲み、動的にリトライを処理します。

foreach ($largeDataSet as $data) {
    try {
        $stmt = $pdo->prepare("INSERT INTO logs (message) VALUES (?)");
        $stmt->execute([$data['message']]);
    } catch (PDOException $e) {
        if ($e->errorInfo[1] == 2006) {
            // 接続を再確立し、特定のインサートをリトライする
            $pdo = new PDO($dsn, $user, $pass, $options);
            $stmt = $pdo->prepare("INSERT INTO logs (message) VALUES (?)");
            $stmt->execute([$data['message']]);
        } else {
            throw $e;
        }
    }
}

インフラと環境のチェックコードを変更してもエラーが解消されない場合は、OSやコンテナレベルに問題がある可能性があります。tail -f /var/log/mysql/error.log を使用してMySQLのエラーログを確認してください。「mmap can't allocate」や「Out of memory」というメッセージが表示される場合、サーバーの物理RAMが不足しています。Docker環境では、コンテナに十分なメモリ制限が設定されているか確認してください。MySQL 8.0は特にメモリを消費するため、1GB未満に制限されるとクラッシュすることがあります。

検証:ソリューションのテストmax_allowed_packet の修正を検証するには、データベースから非常に大きな文字列を取得してみてください。タイムアウトのテストでは、開発環境で wait_timeout を5秒に設定し、PHPスクリプトに sleep(10) を追加して、再接続ロジックが正常にエラーをキャッチしセッションを復元することを確認します。

Related Error Notes