gorm 不提供显式断线重连 api,依赖 database/sql 连接池自动管理;需通过驱动错误码(如 pq.error.code 以 "08" 开头)精准识别连接异常,配合合理连接池配置(setmaxopenconns、setconnmaxlifetime 等)和健康检查实现可靠连接管理。

GORM 本身不提供“断线重连”的显式 API,也不需要你手动重连 —— 它依赖 database/sql 连接池自动管理连接生命周期。所谓“重连”,其实是连接池在下次请求时自动新建连接的过程,前提是配置得当、错误能被正确识别。
为什么不能靠 db.Error != nil 判断连接断了
GORM 的 db.Error 是操作级错误,比如 SQL 语法错、记录不存在、唯一约束冲突,甚至网络超时都可能包装成通用 error。它不区分“连接已死”和“查询失败”。直接用 if db.Error != nil { reconnect() } 会误判、反复重建连接,甚至掩盖真实业务问题。
常见误判现象:
- 执行
db.First(&u)返回gorm.ErrRecordNotFound,却被当成连接失败触发重连逻辑 - MySQL 报
1040: Too many connections,本质是服务端拒绝新连接,不是客户端连接断开,重连只会雪上加霜 - PostgreSQL 报
ERROR: current transaction is aborted(SQLSTATE25P02),这是事务状态异常,不是连接问题
真正该检测的是底层驱动错误码
必须穿透 GORM 封装,对 result.Error 做类型断言,提取原生驱动错误结构,再按协议判断是否属于连接类异常:
-
PostgreSQL(
github.com/lib/pq):检查*pq.Error.Code是否以"08"开头(如"08006"表示 connection failure) -
MySQL(
github.com/go-sql-driver/mysql):断言为*mysql.MySQLError,检查Number()是否为1040(too many connections)、2002(can't connect to local MySQL server)、2013(lost connection during query)等 -
SQLite / SQL Server:无稳定网络连接概念,一般不需“重连”,但可捕获
os.IsTimeout或 I/O 类错误作兜底
示例(PostgreSQL):
import "github.com/lib/pq"
func isConnError(err error) bool {
var pqErr *pq.Error
if errors.As(err, &pqErr) {
return len(pqErr.Code) >= 2 && pqErr.Code[:2] == "08"
}
return false
}
result := db.First(&user)
if result.Error != nil && isConnError(result.Error) {
// 此时才适合触发监控告警或健康检查刷新
// 不要在这里调用 gorm.Open!
}
连接池配置决定“重连”是否生效
即使检测出连接异常,若连接池配置不合理,新请求仍会卡住或失败。关键参数必须显式设置(通过 db.DB() 获取底层 *sql.DB):
-
SetMaxOpenConns(n):限制最大并发连接数,避免打爆数据库(MySQL 默认无限,建议设为 20–50) -
SetMaxIdleConns(n):空闲连接上限,建议与SetMaxOpenConns相同,减少连接创建开销 -
SetConnMaxLifetime(d):强制回收存活过久的连接(如time.Hour),防止被 DB 端wait_timeout杀掉后复用出错 -
SetConnMaxIdleTime(d)(Go 1.15+):控制连接空闲多久后关闭,比SetConnMaxLifetime更精准
注意:timeout、readTimeout、writeTimeout 等 DSN 参数只影响单次 dial 或 I/O,不控制连接池行为。
别写重连逻辑,重点做健康检查和降级
试图在每次查询失败后“重试 + 重连”是反模式。GORM 和 database/sql 已在底层做了连接复用与失效剔除。你真正该做的只有三件事:
- 在应用启动时调用
db.DB().Ping()验证初始连通性 - 在 HTTP handler 或任务入口处,对连接类错误(如上面识别出的
isConnError)记录日志并上报监控(如 Prometheus + Alertmanager) - 配合 Kubernetes Liveness Probe 或自定义健康接口(如
/healthz),返回db.DB().Ping() == nil结果,让基础设施自动重启实例
最易被忽略的一点:**不要在请求中临时调用 gorm.Open 创建新 *gorm.DB 实例**。这会绕过所有连接池配置,快速耗尽 DB 连接数,且无法共享 SetConnMaxLifetime 等设置 —— 错误往往就藏在这个“看似修复”的操作里。











