应设置db.setconnmaxidletime(3time.minute)和db.setconnmaxlifetime(240time.second)配对使用,前者控制空闲连接回收时机,后者确保连接在服务端wait_timeout前退役,避免复用失效连接报invalid connection或driver: bad connection。
database/sql连接池空闲连接被服务端断开怎么办
mysql 或 postgresql 服务端会在空闲超时后主动关闭连接,而 database/sql 连接池默认不验证连接有效性,导致下次复用时报 invalid connection 或 driver: bad connection。这不是连接池“坏了”,而是它没做连接保鲜。
- 必须配对设置:
db.SetConnMaxIdleTime(3 * time.Minute)(Go 1.15+) +db.SetConnMaxLifetime(240 * time.Second),前者控制空闲多久回收,后者强制连接在服务端wait_timeout(MySQL)或tcp_keepalives_idle(PG)前退役 - MySQL 默认
wait_timeout = 28800(8 小时),但线上常调小到 300 秒;此时ConnMaxLifetime建议设为 240 秒,留 60 秒缓冲 - 别用
db.Ping()做保活——它只检查池中任一连接是否通,不保证你即将拿到的那个有效,且高频调用反而加重 DB 负担
为什么设置了SetMaxOpenConns还是报connection pool exhausted
现象是日志里频繁出现 sql: connection pool exhausted,但 netstat -an | grep :3306 | wc -l 显示 DB 端连接数远未打满。这基本不是连接不够,而是连接没被释放回池。
-
rows, err := db.Query()后忘记defer rows.Close():连接卡在 idle 状态,直到ConnMaxIdleTime到期才清理 - 事务中 panic 导致
tx.Commit()或tx.Rollback()没执行:该连接被tx持有,无法归还 -
defer rows.Close()写在 if 分支里(如if err != nil { ... }之后),部分执行路径跳过关闭 - 推荐写法:
if rows, err := db.Query(...); err != nil { ... } else { defer rows.Close() },确保所有成功路径都覆盖
SELECT失败后能直接重试db.QueryRow吗
可以,但必须区分错误类型——只有幂等读操作才能安全重试,写操作盲目重试会引发重复插入或状态错乱。
- 先判断错误是否属于连接类:
strings.Contains(err.Error(), "invalid connection") || strings.Contains(err.Error(), "driver: bad connection") - 确认是 SELECT 类查询后,重试应使用新上下文(避免复用旧连接):
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second),再调db.QueryRowContext(ctx, ...) - INSERT/UPDATE 不要重试 SQL 本身,改用
INSERT ... ON DUPLICATE KEY UPDATE或应用层带idempotency_key字段 + 唯一索引兜底 - 别封装“自动重连 + 重放”逻辑:
database/sql不提供连接上下文透传,你无法知道上次 SQL 是否已执行成功
PostgreSQL和MySQL在连接池配置上有什么关键差异
底层机制不同,参数语义不能直接照搬。MySQL 看 wait_timeout,PostgreSQL 看 TCP keepalive 和服务端配置,驱动行为也有区别。
- MySQL:重点对齐
wait_timeout和interactive_timeout,ConnMaxLifetime必须小于二者最小值;ConnMaxIdleTime可设为相同或略短 - PostgreSQL:
ConnMaxLifetime应小于tcp_keepalives_idle(默认 7200 秒),但更关键的是配合pgxpool使用——它会在取连接时自动Ping(),而原生database/sql+lib/pq不做这个 - 代理层(如 ProxySQL、HAProxy)会引入额外空闲超时,此时
ConnMaxLifetime必须小于代理配置,否则连接在代理侧先被 kill,Go 侧仍认为可用 - MySQL 驱动(
go-sql-driver/mysql)对invalid connection的触发更敏感;PG 驱动(lib/pq)有时会卡在 read 等待,需靠 context 超时兜底
连接池本身不感知网络中断或服务端单边断连,所有“修复”本质都是预防性老化 + 错误分类重试。最容易被忽略的点是:把 ConnMaxLifetime 当成 SQL 执行超时用,但它只管连接存活时间,防慢查询还得靠 QueryContext 和 context.WithTimeout。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











