hyperf mysql连接池溢出根本原因是max_connections、wait_timeout、min_connections与协程生命周期未对齐;需按峰值qps×p95耗时+定时任务×2+websocket并发×1.5计算max_connections,wait_timeout设5.0平衡失败快与掩盖问题,min_connections设为max_connections×0.3,max_idle_time严格小于mysql wait_timeout,并优先排查context泄漏。

Hyperf 协程环境下 MySQL 连接池溢出,根本不是“池子太小”或“超时太短”单点问题,而是 max_connections、wait_timeout、min_connections 和协程生命周期三者没对齐。调错一个,其他全乱。
max_connections 不能只看 QPS × 耗时
很多人按 120 QPS × 0.08s = 9.6 就设 max_connections 为 20,结果压测直接爆 WaitTimeoutException。漏掉了三个关键协程场景:
- 定时任务常驻协程长期持有连接,不归还就等于“占坑不干活”
-
$db->transaction()未正常 commit/rollback,连接卡在事务中直到协程结束 - WebSocket 长连接里嵌套 DB 查询,每个连接背后可能挂多个协程 DB 操作
真实公式应为:max_connections ≈ 峰值 QPS × P95 耗时 + 定时任务数 × 2 + WebSocket 并发连接数 × 1.5。例如:QPS=120、P95=0.08s、定时任务 3 个、WS 连接 200,则基础值 ≈ 9.6 + 6 + 300 = 315.6 → 向上取整并留 20% 余量,设为 380。
wait_timeout 设 3.0 是危险默认值
Hyperf 默认 wait_timeout => 3.0,但协程调度抖动、慢查询、锁竞争都可能让排队时间超过 3 秒。这不是连接池“不够”,而是它被堵住了。
wait_timeout :日志刷屏 <code>Hyperf\Pool\Exception\WaitTimeoutException,但ss -s | grep ESTAB显示实际连接数远低于max_connections,说明是瞬时排队失败,不是资源不足-
wait_timeout > 8.0:请求卡住不报错,掩盖了连接泄漏(如pipeline没exec)或事务未提交的真实问题 - 推荐设为
5.0:给慢查询和调度抖动留缓冲,又不至于拖死整个请求链路
改完后必须观察 hyperf_db_pool_used_connections 曲线——如果峰值长期贴着 max_connections 运行,才说明真要扩容;否则先查泄漏。
min_connections 和 max_idle_time 必须联动
min_connections 不是“保底数量”,而是“冷启动期必须预热的连接数”。设太低(比如 1),服务刚起时所有请求都得等建连;设太高(比如等于 max_connections),空闲时白占内存,还可能触发 MySQL 的 max_connections 限制。
-
min_connections建议设为max_connections × 0.3(如max_connections=380→min_connections=114) -
max_idle_time必须小于 MySQL 的wait_timeout(查SHOW VARIABLES LIKE 'wait_timeout'),否则连接会被 MySQL 主动断开,而池子里还留着失效句柄,下次复用直接报MySQL server has gone away - 典型配对:
max_idle_time => 55.0(MySQLwait_timeout=60),idle_timeout => 600(毫秒)防低峰期过度收缩
泄漏比参数更重要:先查 Context 是否干净
90% 的“连接池溢出”其实是连接没归还。Hyperf 的连接池靠协程上下文自动回收,一旦 Context 被污染(比如异常跳出 try 块、return 提前退出),连接就锁死在当前协程里,直到协程销毁(可能几分钟后)。
- 加日志验证:
Context::has($db->getContextKey())在关键路径前后打点,看是否始终为true且未释放 - 高频泄漏点:
$db->transaction()里没写finally { $db->commit() / $db->rollback() };$db->query(...)后没处理ResultSet就 return - 启用泄漏检测:
leak_detection_threshold => 60000(毫秒),生产环境能抓到 60 秒内未归还的连接
参数调得再细,Context 里塞着 50 个没释放的连接,max_connections 设成 1000 也没用——它们根本进不了池子排队。











