Hyperf连接池WaitTimeoutException本质是空闲连接耗尽,而非Redis慢;需通过ss -s或swoole_get_local_socket_count()确认ESTABLISHED连接数是否逼近max_connections,再排查pipeline/transaction未exec、MySQL异常污染上下文等泄漏点,并合理配置heartbeat、max_idle_time与wait_timeout协同参数。

Hyperf连接池等待超时(WaitTimeoutException)本质不是连接慢,而是“没连接可分”——协程排队等不到空闲连接。解决关键不在调大超时时间,而在厘清资源配额、堵住泄漏、匹配真实负载。
先确认是不是真耗尽:查连接使用实况
别只看错误日志,直接验证连接池状态:
- 执行
ss -s | grep ESTAB或swoole_get_local_socket_count(),观察当前 ESTABLISHED 连接数是否持续逼近pool.max_connections - 登录 MySQL,运行
SHOW PROCESSLIST,按host和user分组统计,确认是否某个服务占满连接(比如单个服务用了 180/200) - 查
information_schema.processlist中state = 'Sleep'且time > 180的连接,说明空闲回收失效,连接在池里“假活”堆积
配额必须算清楚:别让所有服务平分总连接数
MySQL 的 max_connections 是硬上限,不是共享资源池。5 个服务各设 max_connections = 100,而 DB 总限制才 200,上线即打满。
- 按服务等级分配:核心订单类服务可占 40%,报表查询类限 15%,定时任务类强制设为 5
- 每个数据库连接需单独配置:在
config/autoload/databases.php里显式写死pool.max_connections,不依赖默认值 - PostgreSQL 场景下,
pool.max_connections建议 ≤ DB 侧max_connections的 70%,留出系统和运维连接余量
堵住连接泄漏:Pipeline、事务、异常是重灾区
协程不退出,泄漏的连接就不会释放。最常见于 Redis 和 MySQL 的 pipeline/multi 操作。
- Redis
$redis->pipeline()或$redis->multi()后,必须确保->exec()执行;异常跳出前未exec,连接会被长期独占 - 正确写法:用
try/finally包裹,或升级到 Hyperf 3.1+ 使用withPipeline()等安全封装 - MySQL 场景中,事务未正常提交/回滚、协程上下文切换导致连接对象被 GC 但 socket 未关,也会造成 Sleep 连接缓慢增长
参数协同要闭环:heartbeat + max_idle_time + wait_timeout 缺一不可
三者不匹配,就会出现“心跳没用”“空闲连接早被 DB 踢了”“协程等不到连接就超时”等问题。
- 查 MySQL 实际
wait_timeout:执行SHOW VARIABLES LIKE 'wait_timeout'(生产常见 300 或 600 秒) -
pool.max_idle_time必须严格小于它,建议设为wait_timeout - 30(单位:秒) -
pool.heartbeat推荐设为max_idle_time / 2(如 max_idle_time=270,则 heartbeat=135),且必须显式设为true或具体数值(false不生效) -
pool.wait_timeout别设太小(如 0.1),推荐 2.0–5.0;它控制协程排队时长,不是 DB 连接超时
冷启动与突发流量要兜底
服务刚重启或流量突增时,min_connections 过小会放大问题。
-
pool.min_connections至少设为 5~10,避免首次请求因池空而排队失败 - 若业务含导出、批量处理等长周期任务,应绕过连接池直连:
DB::connection()->getPdo(),防止占用池内连接过久 - 开启连接泄漏检测,阈值设为 60–120 秒,早于
max_idle_time触发告警











