根本原因是连接泄漏与max_idle_time和wait_timeout配置倒置:Hyperf连接池中大量连接已取出未归还,导致SHOW PROCESSLIST出现大量SLEEP连接,Threads_connected长期逼近pool.max_connections;max_idle_time必须严格小于MySQL的wait_timeout(建议设为wait_timeout−30),heartbeat应≤max_idle_time且推荐设为一半,pool.wait_timeout宜设5.0秒以平衡异常暴露与业务容忍。

Hyperf 项目在携程这类高并发场景下出现 MySQL 连接数超限,根本不是“池子开小了”这么简单——90% 的 Too many connections 错误背后,是连接泄漏 + max_idle_time 与 MySQL wait_timeout 配反 + wait_timeout(客户端等待)被设成 3.0 秒这种反模式组合导致的连锁假象。
为什么 max_connections=100 还爆 Too many connections
这不是数据库拒绝新连接,而是 Hyperf 连接池里堆积了大量“已取出但未归还”的连接。典型表现是:SHOW PROCESSLIST 里大量 SLEEP 状态连接持续存在,且 Threads_connected 长期贴近你设的 pool.max_connections。
- 查真实瓶颈:用
swoole_get_local_socket_count()或ss -s | grep ESTAB看 PHP 进程实际 ESTABLISHED 连接数,若远低于pool.max_connections,说明不是池不够,是连接卡在业务逻辑里没释放 - 高频泄漏点:
$db->transaction()里没写finally块、$db->query()后跟了sleep(5)类阻塞操作、Redis pipeline 未exec()就抛异常 -
min_connections=0会导致冷启时所有协程抢建连接,瞬间打满 DB;必须设 ≥2,让池子有缓冲余量
max_idle_time 和 MySQL wait_timeout 怎么配才不翻车
Hyperf 的空闲连接回收只靠 IdleConnectionChecker 线程,它不会主动探测,只按 max_idle_time 定时清理。一旦配错,连接在池里“睡过头”,取出来就报 MySQL server has gone away。
- 先查 MySQL 实际值:
SHOW VARIABLES LIKE 'wait_timeout';(注意不是interactive_timeout,Hyperf 连接默认非交互) -
max_idle_time必须严格小于wait_timeout,建议设为wait_timeout - 30(比如 MySQL 是 60,你就设 30) -
heartbeat不是常驻心跳,只在连接取出时单次PING;它必须 ≤max_idle_time,推荐设为max_idle_time / 2(如 max_idle_time=30,则 heartbeat=15) - 别碰
checker => 'SELECT 1':这个语句不能在事务中执行,一用就崩;只有驱动真不支持PING才考虑
pool.wait_timeout 设 2.0~5.0 秒的底层逻辑
这个值决定协程排队等连接的容忍极限。设得太小,瞬时抖动就满屏 WaitTimeoutException;设得太大,会掩盖真正的问题——比如连接泄漏或 SQL 慢查询把连接锁死几十秒。
wait_timeout (如 0.5):协程刚进队列就被踢出,日志刷屏但根本没机会拿到连接-
wait_timeout > 8.0(如 15):请求卡住十几秒才失败,运维根本分不清是 DB 慢还是池子空 - 生产环境推荐 5.0:给事务/复杂查询留出合理耗时波动空间,同时确保异常能快速暴露
- 配合
connect_timeout:线上网络抖动常见,connect_timeout建议从默认 10.0 降到 5.0,避免阻塞协程太久
PostgreSQL “Too many clients” 不是驱动问题
这个错误是 PostgreSQL 服务端明确拒绝新连接,源头永远在 postgresql.conf 的 max_connections 耗尽,或客户端连接没归还。和 ext-pgsql 版本无关,盲目升级反而可能引入 aio_thread failed 这类协程调度 bug。
- 查 DB 实际限制:
SHOW max_connections;(注意语法和 MySQL 不同) - Hyperf 的
pool.max_connections必须 ≤ DB 侧max_connections的 70%,预留空间给备份、监控、DBA 临时登录 - PostgreSQL 没有
wait_timeout,对应的是tcp_keepalives_idle,别拿 MySQL 那套去配 PG - 如果
pg_stat_activity里大量idle in transaction,说明代码里BEGIN了没COMMIT/ROLLBACK,比连接池配置更致命
最易被忽略的其实是 pool.connect_timeout 和 min_connections 的联动:网络抖动时 connect_timeout 还设 10.0,协程会在连接建立阶段卡死;而 min_connections=1 会让所有协程在启动初期争抢首个连接,形成尖峰。这两个值必须一起调,不是单点优化的事。



















