事务必须用独立的Orm实例,不能复用全局实例;Beego中需每次请求创建新实例,Begin()后所有操作必须基于同一实例,混用会导致脱离事务上下文,且不支持嵌套事务,须透传实例并显式错误处理与回滚。

事务必须用独立的 Orm 实例,不能复用全局实例
Beego 的 orm.NewOrm() 默认返回无状态的普通实例,但事务需要有状态的 TxOrm。一旦调用 Begin(),ORM 内部会切换为事务模式,并绑定当前数据库连接——这个连接会被后续所有操作(Insert、Update、QueryTable)复用。如果在事务中混用另一个 orm.NewOrm() 实例,它会走新连接,完全脱离事务上下文。
常见错误:在 Begin() 后又写 o2 := orm.NewOrm() 去查数据或更新,结果提交时发现部分操作没生效——因为它们根本不在同一个事务里。
- 正确做法:整个事务流程只用一个
o := orm.NewOrm()实例,且从Begin()开始到Commit()/Rollback()结束,中间所有 ORM 调用都基于它 -
Begin()返回error,必须检查;若失败直接返回,不要继续执行后续插入逻辑 - 事务内禁止调用
o.Using("other_db")或切换数据库别名,这会破坏事务一致性
Rollback() 必须在 panic 或 error 后立即触发,defer 不可靠
很多示例用 defer o.Rollback() 包裹整个函数,看起来简洁,但实际极易漏掉回滚:只要事务中途某处没 panic 或没走到 defer 作用域(比如提前 return),Rollback() 就不会执行,连接还可能被复用导致脏数据。
更危险的是 recover 机制写错位置——例如把 defer func() { if r := recover(); r != nil { o.Rollback() } }() 放在 Begin() 之前,那 Begin() 失败时根本不会进 recover 分支。
- 推荐写法:显式判断每一步错误,出错立刻
o.Rollback()+return - 如果坚持用 panic/recover,
recover()的 defer 必须紧贴Begin()之后,且确保Try块内所有 ORM 操作都在同一作用域 -
Rollback()本身也返回error,虽然通常忽略,但日志里至少记一笔“回滚失败”,避免误以为成功
嵌套事务不被支持,多层函数调用需透传 Orm 实例
Beego ORM 没有 savepoint 机制,Begin() 在已有事务中再次调用会报错或静默失败(取决于驱动)。这意味着你不能在 DAO 层函数里自行 Begin(),否则上层业务的事务会被打断。
典型场景:主函数开启事务,调用 AddOrganization(),而后者内部又调用了 AddNetworkOrg() —— 如果后者自己 Begin(),整个流程就乱了。
- 解决方式:所有参与事务的函数都接收
o orm.Ormer参数,由最外层统一管理生命周期 - DAO 函数签名应类似
func InsertUser(o orm.Ormer, u *User) (int64, error),而不是自己 new Orm - 切勿在事务中调用其他已封装了
Begin()的工具函数,除非你确认它是幂等或明确声明“仅用于非事务场景”
事务超时和连接泄漏是生产环境最大隐患
Beego 不自动设置事务超时,MySQL 默认 wait_timeout 是 8 小时,但长事务会占用连接池资源。更常见的是忘记 Commit() 或 Rollback() 导致连接卡住,连接池耗尽后整个服务拒绝数据库请求。
尤其注意异步 goroutine 中启动事务:Go 的 defer 在 goroutine 退出时才执行,主线程早结束了,连接却一直挂着。
- 务必在事务函数末尾强制加
defer func() { if o != nil { o.Rollback() } }()作为兜底(即使前面有显式 Rollback) - 在数据库配置里显式设
SetMaxIdleConns和SetMaxOpenConns,避免连接数失控 - 监控
SHOW PROCESSLIST中Command=Sleep且Time特别大的连接,基本就是漏处理的事务











