“No available connection”是连接池耗尽结果,需用swoole_tracker+协程堆栈定位持连协程;Db::transaction()异常未捕获会导致连接泄漏;非协程环境调用DB会绕过连接池;通过db:pool-status和Threads_connected对比可区分配置不足与真实泄漏。

Hyperf连接池报“No available connection”时,怎么确认是哪个协程占着连接不放
直接看日志没用——No available connection只是结果,不是原因。真正要查的是:谁拿了连接、没还、还卡在哪儿了。Hyperf 本身不记录“谁持有了哪条连接”,得靠 Swoole 底层 + 协程堆栈联动定位。
关键动作不是开 DEBUG 日志级别,而是启用 swoole_tracker 并配合协程状态快照:
- 确保
php.ini中已开启:tracker.enable_malloc_hook=1和tracker.enable_memcheck=1 - 启动服务时加环境变量:
SWOOLE_TRACE=1 php bin/hyperf.php start(仅开发环境,生产慎用) - 复现问题后,用
swoole_get_coroutine_list()获取当前所有活跃协程 ID 列表 - 对每个 CID 调用
swoole_coroutine_get_stackframe($cid),重点筛出含Db::、beginTransaction、pdo->query的堆栈
如果堆栈里出现 sleep()、curl_exec() 或自定义同步 IO 调用,基本就是它——协程被阻塞,连接被锁死在 PDO 实例里,无法自动归还。
Db::transaction() 里异常未被捕获,连接会泄漏吗
会,而且非常隐蔽。Hyperf v2.2+ 虽增强了连接回收,但 Db::transaction() 的闭包执行失败时,若没显式 rollback(),PDO 连接不会自动释放回池。
典型错误写法:
Db::transaction(function () {
Db::table('order')->insert([...]);
// 这里抛出异常,比如 Redis 连接失败
$this->cache->set('key', 'val');
Db::table('log')->insert([...]); // 这行根本不会执行
});
// 异常向上冒泡,事务闭包退出,但连接未 rollback 也未 commit
正确做法必须带 try-catch 或用 finally 显式兜底:
- 用
Db::transaction()时,闭包内所有操作都应是可回滚的;非 DB 操作(如缓存、HTTP 请求)建议挪到事务外 - 若必须混合,用
try { ... } catch (\Throwable $e) { Db::rollBack(); throw $e; } - 更稳妥的是手动控制:
Db::beginTransaction()→ 操作 →Db::commit()/Db::rollBack(),全程自己掌握生命周期
为什么开了 enable_pool=true 还是频繁新建 PDO 连接
大概率是代码跑在非协程上下文里了。Hyperf 的连接池只在协程中生效,一旦脱离协程,Db:: 会退化为每次 new PDO。
常见触发场景:
- Swoole\Timer::tick() 回调里直接调
Db::,没用Co::create()包一层 - 自定义进程(
AbstractProcess)中未显式启动协程就执行 DB 查询 - 单元测试里用
Mockery或PHPUnit默认非协程模式运行,Db::调用全部走同步路径
验证方法:在疑似位置加一行 var_dump(Co::getCid());,返回 false 就说明不在协程中——这时候任何 Db:: 都绕过连接池。
连接池耗尽时,如何快速判断是配置太小还是真有泄漏
看 max_connections 和当前活跃连接数的比值,再结合连接空闲时间分布。
执行以下命令观察实时水位:
-
php bin/hyperf.php db:pool-status(需安装 hyperf/database-tools) - 或直连 MySQL 查:
SHOW STATUS LIKE 'Threads_connected';对比配置的max_connections - 用
swoole_get_local_socket_count()看 ESTABLISHED 数是否持续高于业务并发量
如果 Threads_connected 接近上限,且 db:pool-status 显示 used: max_connections、idle: 0,但业务 QPS 很低(比如只有 5),那基本可以断定是泄漏——连接被借走后没归还,不是不够用。
真正容易被忽略的是:连接池里的连接可能“活着但失效”,比如 MySQL 侧因 wait_timeout 主动断连,而 Hyperf 没及时探测并剔除。这时要检查是否启用了 heartbeat 和 ping_when_get 配置项。


















