go标准库database/sql连接池不支持自动重连,断线后首次使用必报错;可靠恢复需依赖驱动扩展或上层封装,db.pingcontext仅验证单个连接可用性,不刷新其他连接状态,也不保证后续query成功。

Go 标准库 database/sql 连接池本身不处理自动重连,断线后首次使用必报错;所谓“重连”必须由驱动、封装层或业务逻辑显式实现,不能依赖 db.PingContext 或轮询重试循环。
为什么 db.PingContext 不是健康检查的解药
它只验证连接池中「至少一个」连接当前可通,不刷新其他连接状态,也不保证下一次 Query 成功。高并发下,哪怕刚 PingContext 成功,Query 仍可能返回 driver.ErrBadConn 或 "invalid connection" —— 因为那个“可用连接”可能已被 MySQL 服务端按 wait_timeout 关闭,而客户端尚未感知。
- 永远用
db.PingContext(ctx),且ctx必须带超时(如2 * time.Second),否则阻塞无界 - 别把它塞进定时 goroutine 里“保活”,纯属误导,不解决老化连接复用问题
- 它不替代连接生命周期管理,更不触发重连逻辑
SetConnMaxLifetime 必须比数据库 wait_timeout 小
MySQL 默认 wait_timeout = 28800(8 小时),但生产环境常设为 300 秒(5 分钟)。若 Go 连接池未设 SetConnMaxLifetime,或设得 ≥ 300 秒,连接就会在服务端关闭后继续被复用,首次 Query 就失败。
- 推荐值:
db.SetConnMaxLifetime(240 * time.Second),留出 60 秒缓冲 - 设为
0表示永不过期 → 放弃预防,全靠出错重试兜底,风险极高 - 该设置对所有连接统一生效,与是否 idle 无关;PostgreSQL 用户还需同步调
tcp_keepalives_idle等参数
运行时查询失败必须自己控制幂等重试
*sql.DB 内置重试仅在 driver.ErrBadConn 下触发,且最多硬编码 10 次,对网络抖动、主从切换、DNS 变更完全无效。
- 只对
SELECT、GET类幂等操作重试;INSERT/UPDATE/DELETE需结合业务idempotency key或事务状态校验 - 用
context.WithTimeout包裹整个重试流程,总时间建议 ≤ 2 秒 - 退避推荐指数增长:
100ms → 200ms → 400ms… - 只捕获明确错误:
driver.ErrBadConn、net.OpError、"connection refused"、"timeout";认证失败、SQL 语法错不重试
自建连接池或封装驱动时最容易漏掉的三件事
手动实现带重连的连接池(比如包装 net.Conn 或 WebSocket)时,以下三点不处理就会快速耗尽文件描述符或 goroutine:
- 每次重连前必须显式调用
conn.Close()(如果conn != nil),否则旧连接 fd 不释放 - 读写 goroutine 必须监听
ctx.Done()或独立的reconnectCh,不能靠for { time.Sleep() }硬循环阻塞等待 - 心跳定时器(如
time.Ticker)必须在重连前Stop(),否则旧 timer 会持续向已关闭的 conn 发送 PingMessage,导致 panic
最易被忽略的是:WebSocket 重连后,旧的写 channel 若未替换,新 goroutine 往已关闭的 channel 发送数据会直接 panic —— 这类泄漏在压测中几秒内就能打满 ulimit。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











