setconnmaxlifetime必须小于数据库wait_timeout,推荐设为240秒以预留60秒缓冲;db.pingcontext不能替代连接生命周期管理,仅验证至少一个连接可用;运行时查询失败需自行实现幂等重试,启动阶段须用带超时的context阻塞等待。

SetConnMaxLifetime 必须比数据库 wait_timeout 小
连接池里每个连接的“出生时间”起算,超过 SetConnMaxLifetime 就会被强制关闭并新建——这是防老化连接复用的唯一可靠手段。MySQL 默认 wait_timeout = 28800(8 小时),但生产环境通常设为 300 秒(5 分钟)。若 Go 端没设或设得 ≥ 300 秒,连接在服务端关闭后仍被复用,首次 Query 就报 invalid connection。
- 推荐值:
db.SetConnMaxLifetime(240 * time.Second),留出 60 秒缓冲 - 设为
0表示永不过期 → 放弃预防,全靠出错重试兜底,风险极高 - 该设置对所有连接统一生效,与是否 idle 无关;PostgreSQL 用户还需同步调
tcp_keepalives_idle等参数
db.PingContext 不是健康检查的解药
db.PingContext 只验证连接池中「至少一个」连接当前可通,不刷新其他连接状态,也不保证下一次 Query 成功。高并发下,哪怕刚 Ping 成功,Query 仍可能返回 driver.ErrBadConn 或 "invalid connection"——因为那个“可用连接”可能已被 MySQL 服务端按 wait_timeout 关闭,而客户端尚未感知。
- 永远用
db.PingContext(ctx),且ctx必须带超时(如2 * time.Second),否则阻塞无界 - 别把它塞进定时 goroutine 里“保活”,纯属误导,不解决老化连接复用问题
- 它不替代连接生命周期管理,更不触发重连逻辑
运行时查询失败必须自己控制幂等重试
*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 语法错不重试
启动阶段必须阻塞重试,不能靠运行时自动恢复
服务刚起来时数据库还没 ready(比如 Docker Compose 启动顺序错乱、K8s InitContainer 延迟),sql.Open 无感,但第一次 db.PingContext() 必然失败。此时若直接 panic 或返回 error,整个服务就起不来。必须主动等,但不能裸写 for { db.Ping(); time.Sleep() }。
- 用
context.WithTimeout(context.Background(), 30*time.Second)控制总等待窗口 - 每次重试前加指数退避:
1s → 2s → 4s → 8s,避免打爆 DB 健康检查端点 - 只对可恢复错误重试:
timeout、dial tcp、connection refused;遇到password authentication failed这类认证错误应立刻退出 - 重试函数接收已初始化的
*sql.DB,绝不重复调用sql.Open
真正容易被忽略的是:连接池参数(尤其是 SetConnMaxLifetime)和运行时重试逻辑必须配合使用——单靠一方无法覆盖所有断连场景。比如连接老化靠前者,网络抖动靠后者;而启动阶段的依赖等待,又完全是另一套机制。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











