gorm v2默认不重连,因其gorm.db仅复用连接池且不校验连接有效性;必须手动配置sql.db的setmaxopenconns、setmaxidleconns和setconnmaxlifetime,并对幂等读操作加一次带退避的重试。

GORM 本身不自动重连,断线后首次查询会报 connection refused 或 i/o timeout 错误,后续调用可能卡死或 panic —— 必须主动配置连接池与重试逻辑。
为什么默认不重连
GORM V2 的 gorm.Open 返回的 *gorm.DB 是一个复用连接池的句柄,但它不监听底层连接状态。MySQL 连接空闲超时(如 wait_timeout)或网络闪断后,连接对象仍被池持有,下次 db.First() 可能直接复用已失效连接,触发底层驱动错误。
- Go 标准库
database/sql的连接池只负责“借出/归还”,不校验连接有效性 - GORM 没有内置心跳或预检机制,
db.Exec("SELECT 1")这类探测需手动加 - V1 的
SetConnMaxLifetime在 V2 中已移至底层*sql.DB,GORM 不接管该行为
必须设置的三个连接池参数
在调用 gorm.Open 后,立即从 *gorm.DB 提取底层 *sql.DB 并配置,否则断线后无法恢复:
-
db.SQLDB().SetMaxOpenConns(50):避免连接数突增压垮数据库 -
db.SQLDB().SetMaxIdleConns(20):空闲连接数过少会导致频繁新建连接,加剧断线感知延迟 -
db.SQLDB().SetConnMaxLifetime(30 * time.Minute):强制连接在 30 分钟后销毁并重建,规避 MySQLwait_timeout(默认 8 小时)导致的静默失效
注意:SetConnMaxLifetime 是最有效、最低成本的“软重连”手段,比自定义重试更可靠。
查询失败时如何安全重试
单纯捕获 driver.ErrBadConn 并重试一次不够 —— 它可能在事务中、或关联预加载场景下重复执行副作用。推荐在 DAO 层封装带上下文的重试:
- 仅对幂等操作(如
First、Find、Count)启用重试,写操作(Create、Save)禁用 - 使用
errors.Is(err, sql.ErrConnDone)或strings.Contains(err.Error(), "i/o timeout")判断是否值得重试 - 最多重试 1 次,且第二次调用前加
time.Sleep(10 * time.Millisecond)避免雪崩
示例片段:
func FindUser(db *gorm.DB, id uint) (*User, error) {
var u User
err := db.First(&u, id).Error
if err != nil && isNetworkError(err) {
time.Sleep(10 * time.Millisecond)
err = db.First(&u, id).Error
}
return &u, err
}
<p>func isNetworkError(err error) bool {
return errors.Is(err, sql.ErrConnDone) ||
strings.Contains(err.Error(), "connection refused") ||
strings.Contains(err.Error(), "i/o timeout")
}</p>
生产环境仍需补充的两个关键点
光靠连接池参数和重试还不够。真实高并发场景下,容易忽略的是:
- MySQL 服务端配置必须匹配:确认
wait_timeout和interactive_timeout不低于客户端SetConnMaxLifetime,否则连接会在客户端清理前被服务端主动 kill - 健康检查接口要直连
*sql.DB.PingContext,而不是调db.Exec("SELECT 1")—— 后者走 GORM 中间层,可能掩盖连接池底层问题
断线不是小概率事件,而是分布式系统的常态。把重连当成基础设施来配,而不是等报错再补救。











