连接“超时断开”是因MySQL服务端主动关闭空闲连接(如wait_timeout=60s),而客户端(如Go连接池)未及时感知,仍缓存失效连接,复用时才报invalid connection;本质是两端连接状态不同步。

为什么连接会“超时断开”而不是报错就完事
MySQL 服务端默认 wait_timeout=28800(8 小时),但生产环境常被调低到 60~300 秒;当 Go 连接池里的空闲连接超过这个时间没被复用,MySQL 主动发 FIN 断开,而 Go 端还把它当“活连接”缓存着。下次一用,就报 invalid connection 或 i/o timeout ——这不是网络问题,是连接状态不同步。
SetConnMaxIdleTime 必须设,且要小于 MySQL 的 wait_timeout
Go 1.15+ 才支持 SetConnMaxIdleTime,它是唯一能主动清理“空闲太久”的连接的机制:
-
SetConnMaxIdleTime只对进入空闲队列的连接生效,所以必须搭配SetMaxIdleConns(比如设为SetMaxIdleConns(20)) - 值建议设为 MySQL
wait_timeout的 75% 左右:DB 端是 60s,Go 端就设45 * time.Second - 不能设太短(如
5 * time.Second),否则空闲连接刚建好就被杀,徒增 TCP 握手开销 - DSN 里的
timeout、readTimeout参数完全无关,它们只管新建连接阶段
SetConnMaxLifetime 是防“静默老化”的兜底手段
有些中间件(如 ProxySQL、RDS 代理、防火墙)会在连接空闲时悄悄断连,却不发 FIN,导致 Go 端永远收不到 EOF。这时 SetConnMaxLifetime 就起作用了:
- 它强制连接在指定时间后**无条件重建**,不管是否空闲、是否还在用
- 推荐设为
time.Hour,比SetConnMaxIdleTime更粗粒度,但更可靠 - 注意:它和
SetConnMaxIdleTime不冲突,两者可共存,分别覆盖不同断连场景
别信 Ping() 能解决一切,它只适合启动校验
很多人在每次查询前加 db.Ping(),以为能提前发现失效连接——这是错的:
-
Ping()是同步阻塞调用,高并发下会显著拖慢响应 - 它检测的是“当前连接是否可用”,但无法保证下一毫秒还可用(中间件可能随时掐断)
- 真正该做的是:启动时调一次
db.Ping()确认通路;运行期靠SetConnMaxIdleTime+SetConnMaxLifetime预防;出错时按错误码重试(如 PostgreSQL 的"08"前缀) - 错误处理必须做类型断言,不能只看
err != nil——invalid connection和duplicate key都走同一个 err 分支,但恢复策略完全不同
连接池配置不是设完就高枕无忧的事,SetConnMaxIdleTime 和 SetConnMaxLifetime 的数值必须和 MySQL 实际 wait_timeout 对齐,差 1 秒都可能让连接卡在 CLOSED_WAIT 里几小时。


















