sql.DB不是单个连接而是连接池管理器,必须配置SetMaxOpenConns和SetMaxIdleConns(m≤n),否则Go 1.12+会panic;SetConnMaxLifetime控制连接最大存活时间而非保活,需小于数据库wait_timeout;db.Ping()仅验证首次连接,不能反映池中空闲连接健康状态。

sql.DB 本身是连接池,不是单个连接;不配参数就上线,大概率在压测或高峰时出 driver: bad connection、context deadline exceeded 或数据库直接拒连。
SetMaxOpenConns 和 SetMaxIdleConns 必须一起设,且顺序不能错
先调 SetMaxOpenConns(n),再调 SetMaxIdleConns(m),且必须满足 m ≤ n。Go 1.12+ 会严格校验,否则运行时 panic,错误信息是 "max idle conns exceeds max open conns"。
- 常见误配:
SetMaxOpenConns(5)后跟SetMaxIdleConns(10)→ 立即 crash - 只设
SetMaxIdleConns不设SetMaxOpenConns→MaxOpenConns默认为 0(无上限),空闲连接池看似生效,但 DB 端可能瞬间被打满 - 生产建议值:
SetMaxOpenConns(100)配SetMaxIdleConns(50),或按 1/2~2/3 比例浮动;MySQL 默认max_connections=151,别超 100~120
SetConnMaxLifetime 不是保活,是“到期就扔”
这个参数控制连接从创建起最多活多久,到期后连接会在归还池时被关闭,下次需要时新建——它不发任何 SQL,也不探测是否还通。
- 设为 0(默认)→ 连接永不回收,容易遇上 DB 侧
wait_timeout或代理(如 RDS Proxy)主动断连,复用时直接报invalid connection - 推荐值:30~60 秒(短平快服务)或 30 分钟(长任务型),但必须略小于 DB 实际超时值(比如 MySQL
wait_timeout=300,则设240 * time.Second) - 注意:它只影响空闲连接的寿命;正在执行 Query 的连接不受影响,超时后等执行完才关
db.Ping() 只验首次建连,不反映池子真实健康状态
db.Ping() 只拿一个连接执行 SELECT 1,成功了不代表池里其他空闲连接都可用。线上常见 ping 成功但后续 query 随机失败,本质是空闲连接已被 DB 断开,而 Ping() 并不检查它们。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 别把
db.Ping()当 readiness probe 放进 k8s —— 滚动更新时可能误判服务不可用 - 真正可靠的健康检查得模拟业务路径,例如
db.QueryRow("SELECT 1").Scan(&i) - 若想让空闲太久的连接自动释放,需配合
SetConnMaxIdleTime(Go 1.15+),比如设为10 * time.Second,避免长期空闲却占着槽位
事务中用 tx.Query,别用 db.Query
事务对象 *sql.Tx 持有从池中取出的专属连接,如果在事务里误调 db.Query,会导致该连接被重复占用或泄漏。
- 忘记
tx.Commit()或tx.Rollback()→ 连接不会归还池,Stats().InUse持续上涨,最终阻塞新请求 -
db.Query在事务中可能拿到另一个连接,破坏事务一致性,甚至触发死锁 - 所有操作必须走
tx.Query/tx.Exec,且确保 defer 或显式调用结束方法
最常被忽略的一点:连接池参数不是一次性配完就完事的,得靠 db.Stats() 持续看 InUse、WaitCount、MaxIdleClosed 这几个值,才能判断是不是真调对了——尤其当加了 LB、ProxySQL 或云数据库代理时,ConnMaxLifetime 得比代理层空闲超时再砍掉 10~20%。

















