必须全局只调用一次gorm.Open()并配置SetMaxOpenConns、SetMaxIdleConns和SetConnMaxLifetime,初始化后立即db.Ping()校验,否则连接池泄漏导致文件描述符耗尽和MySQL报Too many connections。

连接池复用失效,几乎全是代码写法导致的——gorm.Open() 被反复调用、db 实例没全局复用、或连接池参数压根没设。这不是 GORM 的 bug,是资源管理失控的典型表现。
gorm.Open() 被重复调用
每次调用 gorm.Open() 都会新建一个 *sql.DB 实例和独立连接池,旧池不会自动关闭,db.Close() 又常被遗漏。后果是文件描述符耗尽,MySQL 报 Too many connections。
- 绝对禁止在 HTTP handler、goroutine 或循环里写
db, _ := gorm.Open(...) - 正确做法:启动时初始化一次,用包级变量或依赖注入传入业务结构体(如
type UserService struct { DB *gorm.DB }) - 验证是否只调用了一次:加日志,或检查进程打开的 socket 数量(
lsof -p $PID | grep tcp | wc -l)
连接池三参数全为默认值
SetMaxOpenConns 默认 0(无上限)、SetMaxIdleConns 默认 2、SetConnMaxLifetime 默认 0(永不过期),这三者不显式配置,等于把连接池交由运气托管。
-
SetMaxOpenConns必须设,建议取 MySQLmax_connections的 60%~80%;云数据库更保守,按 CPU 核数 ×20 估算 -
SetMaxIdleConns必须 ≤SetMaxOpenConns,否则 Go 1.12+ 直接 panic:"max idle conns exceeds max open conns" -
SetConnMaxLifetime推荐 2~5 分钟(如2 * time.Minute),避免 NAT 断连后复用僵死连接
没做启动时连接健康校验
gorm.Open() 只初始化结构,不建真实连接。服务日志显示 “DB initialized”,但第一次查询才真正拨号——此时网络不通、密码错、DNS 失败,全卡在首请求上,导致 P99 延迟飙升或首屏白屏。
- 初始化后必须立即调用
db.Ping(),失败则 panic 或阻塞重试 - 运行中可定期(如每 30 秒)执行
db.DB().PingContext(ctx)并记录日志 - 别只 check
err != nil,要捕获具体错误类型,比如net.OpError或mysql.MySQLError编号 2003
事务内误用 db.Begin() + defer tx.Rollback()
手写 tx := db.Begin() 后只靠 defer tx.Rollback(),会导致成功时误回滚;panic 时又无法自动 recover,连接卡死,后续请求 hang 住。
- 正确写法:用
if tx.Error != nil判断开启失败;成功后显式tx.Commit(),失败才tx.Rollback() - 若需 panic 安全,改用
db.Transaction(func(tx *gorm.DB) error { ... }),它内部有 recover 逻辑 - SQL Server 用户额外注意:连接串必须带
autocommit=false,否则Begin()和Rollback()全部失效
最常被忽略的一点:连接池复用不是“开了就行”,而是“配得对、用得稳、检得勤”。哪怕参数全设对了,漏掉一次 db.Ping(),上线后第一个流量高峰就可能触发连接雪崩。


















