错:*gorm.DB是连接池句柄,需显式db.Close()释放;反复gorm.Open导致连接泄漏,正确做法是全局单例复用并配置db.DB().SetMaxOpenConns等参数。

gorm.Open 之后不手动管理 *gorm.DB 就以为“自动释放”?
错。GORM 的 *gorm.DB 实例本身不是连接,而是连接池的句柄;它不持有单个数据库连接,也不在超出作用域时自动关闭底层连接池。你调用 gorm.Open 得到的 *gorm.DB 可以(也应该)长期复用,但必须显式调用 db.Close() 才会释放其持有的连接池资源。
常见错误现象:
- 服务运行几小时后出现
dial tcp: lookup xxx: no such host或too many connections - 压测时连接数持续上涨,
show processlist显示大量 Sleep 状态连接 - 程序退出前没调用
db.Close(),导致连接未及时归还、MySQL 等待超时后被动断连
实操建议:
- 全局变量或依赖注入容器中保存单个
*gorm.DB实例,避免反复gorm.Open - 在应用优雅退出(如接收到
SIGINT/SIGTERM)时,调用db.Close() - 不要在每个 handler 或函数里
defer db.Close()—— 这会立刻关掉共享连接池
用 defer db.Close() 关闭连接池?典型反模式
defer 适用于清理当前函数获得的**短期资源**,比如打开的文件、临时事务、一次性的 *sql.Tx。但 *gorm.DB 是长期服务级资源,defer db.Close() 放在初始化函数里,等于刚建好就关掉,后续所有 db.Create() 都会 panic:"cannot call method on closed database"。
正确做法取决于生命周期:
- CLI 工具或一次性脚本:可在 main 函数末尾直接调用
db.Close()(不用 defer) - Web 服务(如 Gin/Fiber):在
server.Shutdown()回调中调用db.Close() - 使用 Wire/GoDI 等依赖注入:把
db.Close()注册为 cleanup 函数,由容器统一触发
连接池参数没调,空闲连接一直占着不放
GORM 底层用 *sql.DB,它的连接池默认行为是“只增不减”——最大连接数设了 MaxOpenConns,但 MaxIdleConns 默认是 2,ConnMaxLifetime 和 ConnMaxIdleTime 默认为 0(即永不过期)。这意味着空闲连接永远留在池里,直到进程退出或 MySQL 主动踢掉。
必须显式配置(通过 db.DB().SetXxx):
-
db.DB().SetMaxOpenConns(20)—— 防止突发请求打爆 DB -
db.DB().SetMaxIdleConns(10)—— 控制空闲连接上限 -
db.DB().SetConnMaxIdleTime(5 * time.Minute)—— 让空闲太久的连接主动释放 -
db.DB().SetConnMaxLifetime(1 * time.Hour)—— 避免连接因网络中间件保活策略失效而卡死
注意:SetConnMaxIdleTime 在 Go 1.15+ 才生效;旧版本需升级或改用 SetConnMaxLifetime 替代。
事务内 defer tx.Rollback() 却忘了 tx.Commit()?
这不是连接池问题,但常和连接泄漏混淆:事务 *gorm.DB(即 *sql.Tx)若没 Commit() 或 Rollback(),连接会被一直占用,直到事务超时或连接被服务端强制中断。
标准写法应是:
tx := db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
if err := tx.Error; err != nil {
tx.Rollback()
return err
}
// ... 业务逻辑
tx.Commit()
更安全的做法是用 sql.Tx 原生风格或封装一个带 defer 的 helper 函数,确保 Rollback 只在非成功路径执行。别依赖 panic 捕获——有些错误不会 panic,比如 tx.Create().Error 返回 error 但不 panic。
真正容易被忽略的是:连接池释放 ≠ 连接立即从 MySQL 断开。MySQL 侧看到的连接可能仍处于 Sleep 状态几秒,这是正常现象;关键看是否持续增长、是否超过 max_connections。盯住 db.Stats().OpenConnections 和 db.Stats().Idle,比看 MySQL processlist 更准。











