ThinkPHP 使用 PDO 连接 MySQL 时,mysqlnd 不主动检测连接超时,导致出现“MySQL server has gone away”而非连接异常;需手动执行 SELECT 1 探测并重连,禁用持久连接,CLI/Swoole 场景须主动管理连接生命周期。

连接超时后 mysqlnd 不抛异常,thinkphp 仍认为连接有效
ThinkPHP 默认用 PDO 连接 MySQL,但底层驱动(如 mysqlnd)在连接断开后不主动检测,导致后续查询报 MySQL server has gone away 而不是连接失败。框架不会自动重连,而是直接抛出异常或返回空结果。
- 常见错误现象:
SQLSTATE[HY000]: General error: 2006 MySQL server has gone away - 触发场景:MySQL 的
wait_timeout或interactive_timeout触发(默认 8 小时),但更常见于长任务、队列进程、CLI 模式下空闲连接 - 关键点:
thinkphp的Db::connect()不做心跳检测,->query()前也不会 ping 连接 - 解决思路:必须在执行前手动检查连接有效性,不能依赖框架自动恢复
Db::connect() 配置里加 'break_reconnect' => true 没用
这个配置项只在「查询执行过程中发生连接中断」时起作用,且仅对部分驱动(如 mysqli)有效;PDO 驱动下基本不生效。它不是“连接前自动重连”,而是“执行失败后尝试重连一次再执行”,但多数情况下失败已不可逆。
- 参数差异:
'break_reconnect' => true与'deploy' => 1(读写分离)组合时逻辑更复杂,容易误判重连时机 - 性能影响:每次查询都可能多一次失败重试,增加响应延迟,且无法避免首次失败的业务异常
- 真实效果:遇到
server has gone away时大概率仍抛异常,而不是静默恢复
手动 ping + 重连的最小可行方案
最可靠的做法是在关键查询前调用 Db::getPdo()->getAttribute(PDO::ATTR_CONNECTION_STATUS) 或直接 ping() —— 但 PDO 本身没 ping() 方法,得退回到底层连接对象。
- 推荐做法:用
Db::getPdo()->exec('SELECT 1')做轻量探测(比getAttribute更准) - 封装建议:在模型基类或中间件中统一拦截,例如重写
BaseModel::getConnection(),加一层连接保活逻辑 - 示例片段:
try { $pdo = Db::getPdo(); $pdo->exec('SELECT 1'); } catch (\PDOException $e) { if (strpos($e->getMessage(), 'MySQL server has gone away') !== false) { Db::close(); // 强制关闭旧连接 Db::connect(); // 触发新连接 } } - 注意:不要在事务中做这个探测,否则可能破坏事务一致性
CLI 进程和长连接场景必须主动管理生命周期
Web 请求天然短生命周期,连接用完即丢;但 CLI(如队列消费者、定时任务)会复用连接数小时,这时超时几乎必然发生。
立即学习“PHP免费学习笔记(深入)”;
- 必须做:启动时记录连接创建时间,在每次查询前检查是否超过
wait_timeout - 30秒 - 不能依赖:
__destruct或register_shutdown_function关闭连接,它们不一定及时执行 - 兼容性提醒:PHP 8.1+ 的
PDO::ATTR_PERSISTENT会加剧该问题,持久连接更难被回收,务必禁用 - 一个容易被忽略的点:Swoole 或 Hyperf 等常驻内存框架里,
Db实例是单例,连接状态跨请求共享,必须自己维护连接健康度
事情说清了就结束。



















