Beego 事务中所有操作必须使用同一个 Ormer 实例:o.Begin() 返回的 TxOrm 是有状态的,后续 Insert/Update/QueryTable/LoadRelated/Raw 等均需复用该 o,否则脱离事务连接,导致脏读、查不到刚插入数据或关联查询不一致。

事务内查询必须用同一个 Ormer 实例
Beego 的事务不是全局上下文,o.Begin() 后返回的是带状态的 TxOrm,所有后续操作(包括查询)必须复用这个实例。如果在事务中又调用 orm.NewOrm(),查到的就是已提交的数据,或者脏读——取决于隔离级别,但绝不是你期望的“事务内快照”。
常见错误现象:o.Insert(&user) 成功,紧接着用新 orm.NewOrm() 查 user 却查不到;或查到了但不是刚插入那条(比如被其他并发事务改过)。
- 正确做法:事务开始后,所有
Insert/Update/QueryTable都用同一个o变量 - 不能跨函数隐式传递:若封装了查询逻辑,需把
o作为参数显式传入 -
QueryTable("user").Filter(...).One()在事务内有效,但必须基于该TxOrm实例
QueryTable 在事务中查不到刚插入数据?检查主键和 auto 标签
Beego ORM 插入后不会自动回填主键值(除非你用 InsertWithCtx 并且数据库支持 LAST_INSERT_ID),而 QueryTable.One() 依赖主键字段匹配。如果结构体里 Id 字段没标 orm:"pk;auto",或类型不匹配(如定义为 int64 但数据库是 BIGINT UNSIGNED),One() 就可能查不到刚插进去的记录。
- 确保主键字段有
orm:"pk;auto",且类型与数据库一致(int对应INT,int64对应BIGINT) - 插入后想立刻查,优先用
o.QueryTable(&User{}).Filter("name", "xxx").One(&u),而不是依赖刚插入对象的Id - 避免用
o.Read(&u, "Id")—— 它不走 QueryTable 流程,也不受事务内连接控制
关联查询(LoadRelated)在事务里会跳出事务吗?
会。默认情况下 LoadRelated 内部会新建一个 Ormer 实例发起查询,不走当前事务连接。结果就是:你查主表是事务内的,但查外键关联的 Profile 表却是从连接池拿的新连接,看到的是已提交数据,破坏一致性。
- 必须手动把事务实例传进去:
o.LoadRelated(&user, "Profile", &orm.RelatedConfig{Ormer: o}) -
RelatedSel()生成 JOIN 查询时也一样,要确认底层是否用了当前o—— Beego v2.3.5 中QueryTable(...).RelatedSel("Profile").All()默认不继承事务,需显式指定Ormer: o - 更稳妥的方式:用原生 JOIN 或子查询,通过
o.Raw(...).QueryRows()手动执行,确保全链路在同一事务连接上
事务中混用 Raw 查询要注意参数绑定和连接归属
o.Raw("SELECT ...").QueryRow(&v) 是安全的,它复用当前 TxOrm 的连接;但如果你写成 orm.NewOrm().Raw(...),就彻底脱离事务了。另外,Raw 不解析结构体 tag,字段名必须和数据库列名完全一致(大小写敏感),否则 QueryRow 会静默失败。
- 事务内 Raw 查询必须以
o.Raw(...)开头,其中o是Begin()返回的那个 - 占位符用
?,不要用$1或:name—— Beego 的RawSeter只支持问号占位 - 批量 IN 查询慎用:如
IN (?, ?, ?),参数个数必须严格匹配,否则报sql: expected 3 arguments, got 2
事务真正生效的关键,从来不是“有没有调 Begin”,而是所有读写操作是否共享同一数据库连接句柄。Beego 不做连接代理或上下文透传,漏掉一个 o. 前缀,就等于主动退出事务。











