connection has been closed 错误真实原因是连接池归还连接时发现其已被数据库、redis或中间设备(如slb、防火墙)主动关闭,常见于mysql wait_timeout触发、redis proxy断连或空闲连接被回收;根本解决需区分并正确配置数据库超时、连接池heartbeat(建议30秒)、max_idle_time(60秒)及min_connections(5~10),禁用错误调小wait_timeout的反模式。

Connection has been closed 错误的真实原因
这个错误不是连接“突然断开”,而是连接池在归还连接时发现它已经被数据库或中间件(如 Redis)主动关闭了。常见于 MySQL 的 wait_timeout 触发、Redis 连接被 proxy 断连、或网络中间设备(如 SLB、防火墙)主动回收空闲连接。
wait_timeout 设太低反而导致 Connection has been closed
很多人看到连接被关,第一反应是把 wait_timeout 调小——这是典型反模式。Hyperf 连接池的 wait_timeout 不是控制数据库侧的空闲超时,而是控制协程在池中“等待可用连接”的最大时长。设得太低(比如 0.1 秒),高并发下大量协程会快速失败并抛出 Connection has been closed 或 No available connection,掩盖了真实问题。
-
wait_timeout建议设为3.0(非默认 0.1),给协程合理排队时间,避免抖动式失败 - 真正要调的是数据库自身的
wait_timeout(MySQL 默认 28800 秒),但一般不动;重点靠连接池的heartbeat主动保活 - 如果业务有长周期任务(如导出、批量处理),需单独用
DB::connection()->getPdo()拿直连,绕过连接池
heartbeat 必须开启且设合理值
不启用 heartbeat,连接池无法感知连接是否已失效,就会把一个已被 MySQL 关闭的连接分配给协程,执行 SQL 时直接报 Connection has been closed 或 server has gone away。
- MySQL 场景:设
heartbeat => 30,每 30 秒发一次PING,比wait_timeout(默认 8 小时)短得多,能提前清理死连接 - Redis 场景:同样设
heartbeat => 30,但注意某些 Redis Proxy(如 Twemproxy)不支持PING,此时应设为-1(禁用)并依赖max_idle_time -
max_idle_time推荐设为60.0,配合heartbeat形成双保险:心跳失败立即剔除,空闲超时自动释放
冷启动和连接泄露会放大这个问题
刚重启服务或流量突增时,min_connections 过小(如设为 1)会导致所有协程争抢唯一连接,一旦该连接因网络抖动或数据库重启失效,整个池就卡住,后续所有请求都报 Connection has been closed。
-
min_connections至少设为5~10,确保冷启动有缓冲连接可用 - 检查代码里有没有忘记
->close()或没用try/finally释放连接(尤其在异常分支) - 用
hyperf:monitor查看db.pool.connections.active和.idle指标,长期只升不降就是泄露
wait_timeout 和数据库自身的连接超时混为一谈,以及在 Redis 集群或代理环境下盲目开启 heartbeat。











