结论是“mysql server has gone away”主因是协程间错误复用连接、mysql服务端wait_timeout断连、客户端未校验状态三者叠加;修复核心为每次query前ping校验、ping失败则新建连接、归还时不close、pop超时设500ms、禁用coroutine::close。

直接说结论:不是“开启协程导致连接失效”,而是协程模型下连接复用方式错了,加上 MySQL 服务端主动断连(wait_timeout 默认 8 小时)和客户端未做状态校验,三者叠加才表现出“连接失效”。修复核心是「每次使用前校验 + 异常时重连 + 归还时不 close」。
为什么 Swoole\Coroutine\MySQL 连接会突然报 “MySQL server has gone away”
这不是偶发网络抖动,而是典型的状态错位:
- 协程 A 创建连接并执行了一条查询,之后 sleep 或切出,但没归还连接;
- 协程 B 从全局变量或错误共享池里拿到这个连接,直接调用
query()—— 此时底层 TCP 连接可能已被 MySQL 主动关闭(比如过了wait_timeout),但 PHP 对象仍认为自己“connected”; -
$mysql->connected属性不可信,它只反映上一次 connect 的结果,不感知服务端断连; - 更隐蔽的是:即使你手动调用了
$mysql->close(),协程销毁时若未显式触发,fd 也不会释放,造成 time_wait 堆积,进一步加剧连接获取失败。
必须在每次 query 前加 ping() 校验
别依赖 $mysql->connected,它完全没用。真正有效的检测只有 ping():
MySQL 9.6.0是面向Linux平台的2026年创新版本,核心架构迎来重大革新。其将外键约束与级联操作从InnoDB引擎层上移至SQL层,确保所有数据变更均被完整记录至Binlog,彻底解决了CDC(变更数据捕获)与主从复制中的数据不一致难题。此外,该版本引入container_aware启动选项以原生适配容器环境,并对审计日志进行了组件化重构,为追求极致数据一致性与云原生体验的开发者提供了全新选择。
<pre class="brush:php;toolbar:false;">$mysql = $pool->pop();
if (!$mysql->ping()) {
// 注意:不能用 $mysql->connect() 重连已有实例
// 必须新建一个连接,否则协议状态机错乱
$mysql = new \Swoole\Coroutine\MySQL();
$mysql->connect($config);
}
$result = $mysql->query('SELECT ...');
$pool->push($mysql);
ping() 是轻量级探测,走 MySQL 协议的 COM_PING 包,不触发权限校验,比 <code>SELECT 1更安全;- 不要试图对已失效的
$mysql实例调用connect()—— 它内部状态已损坏,强行重连大概率 panic; - 如果 ping 失败,直接丢弃该实例,new 一个新的,再 connect;
- 这个逻辑必须包裹在
try/finally里,确保无论成功失败都归还(或丢弃)连接。
连接池 pop() 超时设为 500ms,别用 0
很多人设 $pool->pop(0) 意图“立即返回”,结果在高并发下大量协程卡死在 pop,CPU 拉满却无响应:
- 超时设为
500(毫秒),超过即抛异常,业务层可降级或重试; - 池容量别硬套“最大连接数”,要按平均 QPS × 平均 SQL 耗时 × 安全系数(建议 1.5)来算;例如 QPS=300、平均耗时 20ms → 理论需 9 个连接,池大小设 15 更稳妥;
- 空闲连接存活时间(
max_idle_time)设为 60 秒,避免长期空闲连接被 MySQL 先断; - 绝对不要在业务协程里调用
Coroutine::close()—— 这会导致连接对象生命周期与协程脱钩,fd 泄漏几乎必然发生。
最易被忽略的一点:ping 通过不代表 query 一定成功。MySQL 可能在 ping 后、query 前断连。所以真实项目中,query 失败且 $mysql->errno === 2006 || $mysql->errno === 2013 时,仍要走一遍“丢弃旧实例 + 新建连接”的流程,而不是简单重试 query。










