最危险做法是直接用全局db变量+iris.Context传参,会导致并发下连接泄漏、事务失效、软删除被绕过及静默丢数据;正确做法是在main或init阶段单例初始化*gorm.DB并调优连接池。

直接用全局 db 变量 + iris.Context 传参是最常见但最危险的做法——它会在并发请求下导致连接泄漏、事务失效、软删除被绕过,甚至静默丢数据。
为什么不能在 handler 里反复调用 gorm.Open()
每次 gorm.Open() 都会新建一个独立的连接池,不复用已配置的连接参数(如 SetMaxOpenConns),且无法共享日志、钩子、命名策略等配置。更关键的是:你无法控制它的生命周期,defer db.Close() 在 handler 结束时执行,但底层 *sql.DB 实际上是被 Iris 的 goroutine 复用的,强行 Close 会导致后续请求 panic 报 "sql: database is closed"。
- 不要在每个 handler 里写
db, err := gorm.Open(...) - 不要对同一个 DSN 多次调用
gorm.Open(),哪怕加了锁 -
gorm.Open()是初始化动作,只应发生在init()或main()开始阶段
GetDB() 全局函数 + 连接池配置必须一起用
正确做法是把 *gorm.DB 实例作为单例暴露,但必须确保底层 *sql.DB 已按生产要求调优。Iris 启动前就该完成这一初始化,而不是等第一个请求来才连。
- 在
main.go或database/init.go中定义var db *gorm.DB和func GetDB() *gorm.DB - 调用
db.DB().SetMaxIdleConns(10)、SetMaxOpenConns(100)、SetConnMaxLifetime(time.Hour)—— 缺一不可 - DSN 中必须带
parseTime=True&loc=Local,否则time.Time字段会解析失败或时区错乱 - 别用
gorm.Config{Logger: logger.Default.LogMode(logger.Info)}上生产,至少设为logger.Warn
在 Iris handler 中安全使用 GORM 的三种方式
Iris 的 iris.Context 不自带 DB 注入,所以你需要显式传递。推荐以下顺序选择:
- 最简方案:在
app.UseGlobal()中挂载ctx.SetUserValue("db", GetDB()),handler 内用ctx.UserValue("db").(*gorm.DB)取;适合小项目 - 标准方案:用依赖注入容器(如
github.com/uber-go/dig)注册*gorm.DB,再注入到 controller struct;适合中大型项目 - 最稳方案:把
*gorm.DB作为字段嵌入 controller,初始化时传入,handler 方法接收c iris.Context即可直接调用c.db.First();无第三方依赖,测试友好
无论哪种,都禁止在 handler 里调用 db.Begin() 后又混用 db.Create() —— 必须用返回的 tx 实例做所有操作,且事务函数末尾必须显式 tx.Commit(),否则自动丢弃。
模型定义和自动迁移的坑要提前踩
AutoMigrate 不是“一键同步”,它只建表、加列、改默认值,不会删字段、改类型、删索引。开发期可以开,上线后必须禁用,改用 SQL 迁移脚本。
- 模型结构体必须嵌入
gorm.Model或手动声明ID uint+gorm:"primaryKey",否则First()、Delete()会找不到主键 -
CreatedAt/UpdatedAt字段必须是time.Time类型,且标签不能漏掉gorm:"autoCreateTime"等(新版 GORM v2 默认启用,但老项目可能关了) - 软删除字段
DeletedAt gorm.DeletedAt要加gorm:"index",否则带Unscoped()的查询会全表扫 - 关联字段如
UserID uint必须配gorm:"index",否则Preload()关联查询极慢
真正麻烦的从来不是连上数据库,而是当 db.Transaction 函数里调了 HTTP 请求、写了 Redis、发了邮件,结果事务卡住三秒超时,连接池满,整个 API 就挂了——这些 I/O 操作必须剥离出事务体,用 tx.AfterCommit() 或消息队列兜底。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











