Beego中必须为每个请求创建独立orm.Ormer实例,复用会导致QuerySeter状态污染、事务隔离失效及goroutine不安全;GORM可全局复用*gorm.DB,但不可与Beego ORM混用。

Beego 中复用 orm 实例会导致 QuerySeter 状态污染
多个请求共用同一个 orm.Ormer 实例时,链式调用如 Filter()、OrderBy()、Limit() 会修改实例内部的查询上下文状态。A 请求刚设好 Filter("status = ?", 1),B 请求紧接着调用 OrderBy("-created"),A 的条件可能被覆盖或与 B 的排序混在一起,最终查出错误数据或 panic。
实操建议:
- 务必在每个 Controller 方法内调用
orm.NewOrm()创建新实例,不要在init()或全局变量中初始化 - 若需切换数据库别名,用
o.Using("otherdb"),但该调用必须在新实例上执行,不能跨请求复用后反复切换 - 避免在中间件或 BaseController 中缓存
orm.Ormer,哪怕只读也不安全——因为 Beego ORM 的 QuerySeter 不是 goroutine 安全的只读句柄
事务隔离失效:复用 orm 实例让 o.Begin() 失去意义
事务必须绑定到单次请求生命周期。当多个请求共享一个 orm.Ormer 实例,A 调用 o.Begin() 开启事务,B 在同一实例上调用 o.QueryTable().All(),B 的查询会意外进入 A 的事务上下文(尤其在 MySQL 的 READ-COMMITTED 模式下),导致脏读或不可重复读;更严重的是,A 执行 o.Commit() 后,B 再调用 o.Rollback() 会触发 sql: transaction has already been committed or rolled back panic。
实操建议:
- 事务操作必须和
orm.NewOrm()成对出现,例如:o := orm.NewOrm(); tx := o.Begin(); ...; tx.Commit() - 不要把
tx传给其他函数或 goroutine,事务对象不跨 goroutine 安全 - 若需嵌套事务逻辑,用函数封装并显式传入新
orm.Ormer,而非复用外层实例
GORM 替代方案下,全局 *gorm.DB 是安全的,但别混用
如果你已迁移到 GORM,它的 *gorm.DB 实例是并发安全的,可全局复用。但这不意味着能和 Beego ORM 混用——比如在同一个项目里既用 globalDB.First() 又用 globalOrm.Read(),二者底层连接池、日志钩子、上下文传播机制完全独立,容易造成连接泄漏或事务感知错乱。
实操建议:
- 项目启动时用
gorm.Open()初始化一次*gorm.DB,设置SetMaxOpenConns和SetMaxIdleConns,导出为包级变量 - 控制器中直接使用该全局
db,无需每次gorm.Open(),也无需再调db.Session(&session)除非真有 session 隔离需求 - 彻底移除所有
orm.RegisterDataBase和orm.NewOrm()调用,避免两种 ORM 运行时互相干扰
HTTP handler 内部起 goroutine 时,别把 orm 实例传进去
常见错误是在 Controller 方法里启动 goroutine 处理耗时任务(如发消息、写日志),并把当前 o := orm.NewOrm() 传进去。这看似“新实例”,但 goroutine 生命周期远长于 HTTP 请求,等它真正执行 o.Insert() 时,原请求上下文早已销毁,连接可能已被回收,轻则报 sql: connection is closed,重则触发连接池死锁。
实操建议:
- 异步任务中需要 DB 操作,应在 goroutine 内部重新调用
orm.NewOrm()—— 它开销极小( - 若用 GORM,可直接用全局
db,但注意 GORM 的WithContext()应传入 task 自己的 context,而非 HTTP request.Context() - 绝对不要在 goroutine 中持有 Controller 或 Request 对象的引用,它们不是线程安全的


















