Hyperf协程下“Too many connections”本质是连接池配置与业务不匹配,需优化pool.max_connections、防泄漏、设合理min_connections及避免跨协程共享连接。

Hyperf 协程环境下报 “Too many connections”,本质不是 MySQL 连接数真被用光了,而是 Hyperf 的 MySQL 连接池配置和业务使用方式不匹配,导致连接被长期占用、无法释放或重复创建。重点不在调大 MySQL 的 max_connections,而在于理清连接生命周期和池子行为。
连接池没设上限 or max_connections 过小
Hyperf 默认的 pool.max_connections 是 32,但若业务并发高、单请求耗时长(比如含慢查询、远程调用),实际需要的活跃连接数可能远超这个值。MySQL 报错 “Too many connections” 通常意味着:Hyperf 尝试新建连接时,发现已达到 MySQL 服务端允许的总连接数上限(max_connections),而池中又没有空闲连接可复用。
- 查当前 MySQL 实际连接数:
SHOW STATUS LIKE 'Threads_connected'; - 对比配置:
SHOW VARIABLES LIKE 'max_connections'; - 检查 Hyperf 配置:
config/autoload/db.php中对应数据库的pool.max_connections是否明显低于峰值 QPS × 平均响应时间(例如 200 QPS × 0.2s = 40,建议设为 60~80) - 注意:MySQL 的
max_connections要预留空间给 DBA 工具、监控、备份等非应用连接,别全分给 Hyperf
连接泄漏:没归还、没关闭、异常中断
协程退出时,Hyperf 会自动归还连接——但前提是连接没被“卡住”或“绑定在异常上下文里”。常见泄漏点:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 事务未提交/回滚:DB::transaction() 块内抛出未捕获异常,且没触发 rollback,连接会一直被持有直到协程结束;长生命周期协程(如定时任务、WebSocket)更危险
-
手动获取 PDO 后忘记释放:用了
DB::connection()->getPdo()就绕过了连接池管理,必须自行确保不长期持有 - 嵌套事务或错误重试逻辑:比如在 try/catch 里反复开启事务但未统一清理,容易重复申请连接
- 验证方法:在疑似泄漏接口开头加
throw new RuntimeException();,再看连接数是否持续上涨 —— 若上涨,说明连接根本没走到归还逻辑
min_connections 过低 + 冷启动流量冲击
如果 pool.min_connections 设得太小(比如 1 或 5),服务刚启动或低峰后突增流量时,所有协程争抢极少数连接。一旦其中某个连接因网络抖动、MySQL 重启或 wait_timeout 超时失效,整个池子就可能陷入“想建新连接但已达 MySQL 上限”的死循环。
- 建议
min_connections设为 10~20,保障冷启动缓冲 - 配合
max_idle_time: 30.0和heartbeat: 30,让闲置连接及时回收、失效连接提前剔除,避免“僵尸连接”占着坑不干活 - 别依赖
wait_timeout(连接池级)来控连接数——它只管协程排队时长,不解决连接本身是否健康
跨协程共享连接 or 错误复用
Hyperf 的 MySQL 客户端是协程安全的,但如果你手动把一个 PDO 实例或 Connection 对象存到全局变量、静态属性、或协程上下文外的容器里,再被多个协程并发使用,就会破坏连接池的隔离机制,引发竞争、复用失效连接甚至连接数虚高。
- 禁止将
$pdo或DB::connection()结果赋值给static $conn或ApplicationContext::getContainer()中的单例 - 所有数据库操作应通过
DB::Facade 或注入ConnectionInterface,由框架自动管理生命周期 - 异步任务(如 Job)、定时器、WebSocket 回调中使用 DB,必须确认它们运行在独立协程上下文,且不跨协程传递连接对象

















