多线程PHP脚本使用MySQL出现“MySQL server has gone away”错误排查
我之前帮朋友排查过几乎一模一样的场景,咱们先把核心矛盾捋清楚:你明明是在线程回调里新建的MySQL连接,怎么还会出现连接被断开的问题?答案其实藏在你线程的执行流程里——咱们一步步拆解:
可能的核心原因
你的线程逻辑是:新建MySQL连接 → 发送500条FCM推送 → 更新数据库状态。问题就出在「发送FCM推送」这一步:批量HTTP请求大概率会耗时不短(哪怕单条请求快,500条累计起来也可能有几分钟),这段时间里MySQL连接完全处于空闲状态。如果MySQL的wait_timeout设置得比这个耗时短(有些环境默认是几分钟,而非你以为的8小时),MySQL就会主动关闭这个长期空闲的连接。等你要执行UPDATE的时候,手里握着的已经是失效的连接,自然就抛出“server has gone away”错误了。
另外还有个小概率可能:parallel的线程环境存在隐性的资源残留,但这个可能性远低于「空闲连接被回收」。
针对性解决方案
1. 延迟创建数据库连接(最推荐)
既然问题是“连接创建后空闲太久”,那干脆把创建MySQL连接的时机延后——等FCM推送完全发送完成后,再新建连接更新状态。这样连接创建后立刻执行操作,几乎没有空闲时间,从根源上避免被回收:
// 线程回调优化后的逻辑 function processPushBatch($notifications) { // 第一步:先完成FCM推送发送 sendFCMPush($notifications); // 第二步:推送完成后再新建MySQL连接 $mysqli = new mysqli('你的主机', '用户名', '密码', '数据库名'); if ($mysqli->connect_error) { die('连接失败: ' . $mysqli->connect_error); } // 执行状态更新 $poolId = $notifications[0]['poolId']; $mysqli->query("UPDATE notifications SET status='sent' WHERE poolId='$poolId'"); $mysqli->close(); }
2. 执行更新前检查并重建连接
如果没法调整连接创建时机,可以在执行UPDATE前先验证连接有效性,失效就重新初始化:
// 线程回调中的数据库操作部分 function updateNotificationStatus($poolId) { $mysqli = new mysqli('你的主机', '用户名', '密码', '数据库名'); // 检查连接是否可用,无效则重建 if (!$mysqli->ping()) { $mysqli->close(); $mysqli = new mysqli('你的主机', '用户名', '密码', '数据库名'); } // 执行更新 $mysqli->query("UPDATE notifications SET status='sent' WHERE poolId='$poolId'"); $mysqli->close(); }
ping()方法会快速向MySQL发送一个测试包,验证连接是否存活,无效就重新创建,能直接解决连接被回收的问题。
3. 调整MySQL的空闲超时参数
如果你的批量推送耗时确实较长(比如超过10分钟),可以适当调整MySQL的wait_timeout和interactive_timeout参数(这两个参数建议设成相同值)。比如如果单批次推送最长耗时15分钟,就把超时设成20分钟:
-- 临时生效,重启MySQL后失效 SET GLOBAL wait_timeout = 1200; SET GLOBAL interactive_timeout = 1200;
要永久生效的话,修改MySQL配置文件(比如my.cnf或my.ini):
wait_timeout = 1200 interactive_timeout = 1200
注意别设得太长,避免大量空闲连接占用MySQL资源。
4. 禁用持久化连接
如果你线程里用了PDO的持久化连接(比如PDO::ATTR_PERSISTENT => true),在多线程环境下很容易出现连接泄漏或失效的问题,线程中一定要用非持久化的普通连接。
额外排查小技巧
- 查看MySQL的错误日志,确认是不是因为空闲超时关闭了连接(日志里会有类似
Aborted connection ... due to timeout的记录)。 - 检查线程中创建连接的代码有没有捕获异常,避免默默创建了无效连接却没发现。
内容的提问来源于stack exchange,提问作者Jerem Lachkar

