bad connection本质是客户端复用已被数据库服务端关闭的“过期”连接;需设SetConnMaxLifetime(如4小时)且小于数据库wait_timeout,再配SetMaxOpenConns、SetMaxIdleConns和SetConnMaxIdleTime协同清理空闲连接。

为什么bad connection不是连接池配置错了,而是连接“过期了”
这个错误本质是客户端拿着一个已被数据库服务端关闭的连接去执行 SQL。MySQL 或 PostgreSQL 会在空闲超时后主动断开连接(比如 MySQL 的 wait_timeout=28800),而 GORM 底层的 *sql.DB 如果没设置刷新策略,就会继续复用这条已失效的连接。它和连接池大小、用户名密码无关,也不是 DNS 或网络不通——连接曾经是通的,只是“老死”了。
必须调用SetConnMaxLifetime,且值要小于数据库wait_timeout
这是最直接有效的修复动作。Golang 的 *sql.DB 不会主动探活,只靠这个参数强制连接在指定时间后被丢弃重建。
-
db.DB().SetConnMaxLifetime(time.Hour * 4):设为 4 小时,比 MySQL 默认wait_timeout=28800(8 小时)更保守 - 不能设为
0或负数,那等于禁用该机制 - PostgreSQL 用户注意:
tcp_keepalive参数在驱动里默认不启用,不能替代SetConnMaxLifetime
SetMaxOpenConns 和 SetMaxIdleConns 要配对设,否则空闲连接堆积加剧bad connection
只设 SetConnMaxLifetime 不够。如果空闲连接太多又不回收,它们会在池子里“躺平”到超时,然后一起变成 bad connection。
-
db.DB().SetMaxOpenConns(100):硬上限,建议设为数据库max_connections的 70%~80% -
db.DB().SetMaxIdleConns(50):避免空闲连接长期占位,也减少重建压力 -
db.DB().SetConnMaxIdleTime(time.Minute * 30)(Go 1.15+):比仅靠SetConnMaxLifetime更及时清理“冷连接”
别在每次请求里重试或重连,那是把问题放大
看到 bad connection 就在 handler 里捕获、db.Close()、再 gorm.Open() —— 这是最危险的操作。它会导致:
- 旧连接池没
Close(),内存泄漏 - 新池子不断创建,连接数爆炸,触发数据库
Too many connections - GORM 配置(如日志、回调)被重复加载覆盖
真正该做的是复用全局单例 *gorm.DB,让连接池自己按配置重建连接。复杂点在于错误识别:你得区分 bad connection 和事务异常、约束冲突、记录未找到这些业务错误——它们都走同一个 result.Error 出口,但处理方式天壤之别。











