Beego ORM 不支持嵌套事务,仅能通过手动执行 SAVEPOINT SQL 模拟;其 orm.Begin() 在已有事务中调用会 panic,因底层 database/sql 禁止重复 Begin,必须用 tx.Raw().Exec() 显式管理保存点。

Beego 的 ORM 不支持真正的嵌套事务,所谓“嵌套”只能靠 SAVEPOINT 手动模拟;它底层用的是 database/sql,而标准库在已有事务上调用 Begin() 会直接 panic,所以你不能写 tx.Begin() 或依赖框架自动开子事务。
Beego ORM 中调用 Begin() 在事务内必然 panic
Beego ORM 的 orm.Begin() 底层就是对 *sql.DB 调 Begin()。一旦当前 goroutine 已持有 *sql.Tx(比如外层已开启事务),再调一次就会触发 sql: transaction already in progress —— 这不是 Beego 的 bug,是 database/sql 的硬性限制。
- 错误现象:
panic: sql: transaction already in progress或 PostgreSQL 下报pq: SAVEPOINT can only be used in transaction blocks - 常见诱因:在
orm.DoTx()或手动tx := orm.NewOrm().Begin()后,又在业务逻辑里无意识调了第二次orm.Begin() - 根本原因:Beego 没有像 Django
atomic()或 Laravelsavepoint()那样的封装层,它不拦截或重写Begin()行为
必须手写 SAVEPOINT SQL 并用 tx.Raw().Exec()
Beego ORM 的事务对象 *orm.Ormer(实际是 *sql.Tx)支持 Raw().Exec(),这是唯一可行路径。所有保存点操作都得走原生 SQL,且命名必须唯一、显式管理生命周期。
- 设点:
tx.Raw("SAVEPOINT sp_user_create").Exec()—— 名字别用数字开头,推荐sp_前缀 + 小写随机后缀(如sp_batch_7f2a) - 回滚:
tx.Raw("ROLLBACK TO SAVEPOINT sp_user_create").Exec()—— 注意关键字完整,漏掉SAVEPOINT在 PostgreSQL 里直接报错 - 释放(可选):
tx.Raw("RELEASE SAVEPOINT sp_user_create").Exec()—— MySQL 8.0+ 不支持该语句,SQLite 释放会报错,PostgreSQL 可用但非必需 - 风险点:如果事务已
Commit()或Rollback(),再执行任何ROLLBACK TO SAVEPOINT都会失败,且错误类型因驱动而异(PostgreSQL 报current transaction is aborted,MySQL 静默忽略)
GORM 用户误迁到 Beego 时的典型陷阱
从 GORM 切过来的人容易默认 tx.Begin() 是保存点,但 Beego 没这层抽象。GORM 的 tx.Begin() 实际执行 SAVEPOINT 并返回新 *gorm.DB,而 Beego 的 tx.Begin() 就是死路一条。
- 不要复用 GORM 风格的封装函数,比如带
defer tx.RollbackTo()的 helper —— Beego 没RollbackTo()方法 - 别依赖
orm.WithContext(ctx)控制保存点超时 ——*sql.Tx没有上下文感知的 Query 方法,tx.Raw().Exec()也不接受context.Context - 跨 goroutine 共享同一个
tx并并发设点?危险。Beego ORM 不做保存点命名隔离,名字冲突会导致 MySQL 静默覆盖、PostgreSQL 直接报错
真正难的不是写那三行 SAVEPOINT SQL,而是记住:每个保存点名必须全局唯一(同一事务内)、每次回滚后要确认事务是否仍活跃、释放与否要看数据库类型——这些细节不会报编译错误,但会在凌晨三点的生产环境突然炸出 ERROR 1305。


















