不能在每个 controller 中调用 gorm.open(),因为每次调用都会新建 gorm.db 实例,其底层封装 sql.db 连接池句柄,重复初始化导致连接数暴涨、mysql 报 too many connections、goroutine 泄漏及内存持续增长。

为什么不能在每个 Controller 里调用 gorm.Open()
因为 gorm.Open() 每次都会新建一个 *gorm.DB 实例,而它底层包装了 *sql.DB —— 这个对象本身已是连接池句柄。反复调用会导致:连接数暴涨、MySQL 报 Too many connections、goroutine 泄漏、内存持续增长。你不是在“获取连接”,而是在“新建一堆连接池”。
如何初始化全局 *gorm.DB 实例
必须在项目启动早期(比如 utils/db.go 或 main.go 的 init() 函数)完成初始化,并导出为包级变量:
package utils
import (
"gorm.io/gorm"
"gorm.io/driver/mysql"
)
var DB *gorm.DB
func InitDB() {
dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database: " + err.Error())
}
DB = db
}
- 务必在
main()中调用utils.InitDB(),否则DB为 nil - 不要用
db.Debug()上线,它会打印所有 SQL,拖慢性能并泄露敏感字段 - 若需多库支持,可定义多个全局变量(如
DBMaster/DBSlave),各自独立初始化
Beego Controller 中怎么安全使用 DB
直接导入 utils 包,用全局 utils.DB 即可。它天然支持并发,无需加锁或重连逻辑:
func (c *UserController) Get() {
var users []models.User
err := utils.DB.Find(&users).Error
if err != nil {
c.Data["json"] = map[string]interface{}{"error": err.Error()}
c.ServeJSON()
return
}
c.Data["json"] = users
c.ServeJSON()
}
- 避免在方法内做
utils.DB.Session(&gorm.Session{...})除非真需要隔离上下文(如超时控制) - 事务必须显式开启:
tx := utils.DB.Begin(),后续所有操作都基于tx,最后tx.Commit()或tx.Rollback() - 别把
utils.DB当成 request-scoped 对象传参——它本就是全局、线程安全的
迁移和模型定义要注意什么
GORM 不像 beego/orm 那样自动同步表结构。你需要手动运行迁移,且字段标签语法完全不同:
- 主键必须显式声明:
ID uint `gorm:"primaryKey"`,gorm.Model只是快捷结构体,不自动注册 - 外键关联写法是:
UserID uint+User User `gorm:"foreignKey:UserID"`,不是 beego/orm 的orm:"rel(fk)" - 运行迁移前确保
utils.DB已初始化,再执行:utils.DB.AutoMigrate(&models.User{}, &models.Post{}) -
AutoMigrate不会删除字段或索引,仅新增或修改;删字段要手写 SQL 或用ModifyColumn
最易被忽略的一点:GORM 的预加载(Preload)默认不触发 JOIN,而是发多条 SQL——如果没意识到这点,在高并发列表页上容易触发 N+1 查询,比 beego/orm 的 LoadRelated 更隐蔽。











