hyperf连接泄漏本质是“连上了不还”,90%的too many connections和mysql server has gone away由此引发;典型原因为事务未提交、pipeline未exec、闭包强引用及max_idle_time≥mysql wait_timeout导致空闲连接失效后仍被复用。

Hyperf 协程 MySQL 连接泄漏不是“连不上”,而是“连上了不还”——90% 的 Too many connections 和 MySQL server has gone away 都源于此。
为什么 $connection->query() 执行完连接还在占用?
Hyperf 的 DB 连接池是协程级复用,不是请求级释放。一次 query() 调用后,若没显式归还或协程未退出,连接就一直卡在当前协程上下文里。
- 典型泄漏点:
Db::select()或 ORMget()后直接返回结果,但底层 PDO 连接未触发归还逻辑(尤其配合事务、pipeline 或异常跳出时) - 事务块中未
commit()或rollback()→ 连接被锁死,直到协程结束(可能数分钟) - 闭包中 use 了
$connection或整个$container→ 强引用阻止 GC,连接无法释放 - 定时任务里反复
$this->db->transaction(...)但没 catch 住异常 → 事务中断,连接滞留
max_idle_time 和 wait_timeout 配反了会怎样?
这不是“心跳没开”,而是空闲连接回收机制彻底失效的信号。
-
max_idle_time必须严格小于 MySQL 的wait_timeout(查法:SHOW VARIABLES LIKE 'wait_timeout';),建议设为wait_timeout - 30 - 若
max_idle_time = 120但 MySQLwait_timeout = 60:连接在池里“睡过头”,取出来时已失效,报gone away,且重连逻辑根本没机会触发 -
heartbeat不是后台常驻心跳,只在连接取出时单次PING;真起作用的是 IdleConnectionChecker 线程,它依赖max_idle_time触发清理 - 推荐组合:
max_idle_time = 270,heartbeat = 135,MySQLwait_timeout = 300
怎么确认连接真的泄漏了,而不是配置太小?
别猜,看真实连接数和协程上下文状态。
- 查 MySQL 实时连接:
SHOW PROCESSLIST;,重点关注Command为Sleep且Time> 60 的连接——它们大概率是泄漏的 - 查 Hyperf 进程实际 ESTABLISHED 连接数:
ss -s | grep ESTAB或swoole_get_local_socket_count(),若长期贴近pool.max_connections,说明池子真被占满,不是超时问题 - 在疑似泄漏位置加日志:
var_dump($connection->getContextKey());,再查Context::has($key)是否始终为true—— 若是,连接没归还 - 用
php bin/hyperf.php db:info(Hyperf 3.2+)看连接池统计,in_use持续不降就是泄漏铁证
修复必须改代码,光调配置没用
配置只是兜底,泄漏根因在业务逻辑里。
- 所有数据库操作必须包裹在
try ... finally中,finally里确保$connection->close()或事务收尾(commit/rollback) - 禁用 PDO 缓冲查询:
'options' => [PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false],避免连表查询把 50 万行全 load 进内存再卡住连接 - 定时任务里不要存全局数组:
static $cache = [];→ 改用ApplicationContext::getContainer()->get(CacheInterface::class),否则协程常驻导致连接绑定不释放 - 异步队列 Job 类禁止在
__construct()中注入$container或$logger,改在handle()中按需获取
最隐蔽的泄漏点往往藏在 pipeline() 或 multi() 调用后没 exec(),或者异常跳出了 try 块——这种连接不会自动归还,得靠协程退出才释放,而 Hyperf Worker 进程是常驻的。











