問題:突然の切断深夜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;
}
}
}

