GORM默认对写操作自动开启事务并提交,但若禁用默认事务、显式开启未提交、数据库不支持或查询快照隔离则看似未提交;只读操作不启用事务,事务控制由会话级配置决定且不继承。

GORM 默认就自动提交事务,你不需要手动调用 Commit() —— 但前提是:你没自己开事务,也没禁用默认事务。
为什么有些操作看起来“没提交”?
常见错觉是“改了数据但查不到”,其实不是没提交,而是:
- 你在用
db.Session(&gorm.Session{SkipDefaultTransaction: true})主动关了默认事务,此时 GORM 直接执行单条 SQL,不走事务包装,但也不报错 - 你用了
db.Transaction()或db.Begin()开了显式事务,却忘了tx.Commit(),或 panic 导致 defer 回滚没触发 - 数据库驱动本身不支持自动提交(极少见),比如某些 SQLite 模式下未设
PRAGMA journal_mode = WAL - 你查的是另一个连接或另一个事务快照(例如 MySQL 的 REPEATABLE READ 隔离级别下,新事务看不到未提交的变更)
哪些操作会触发 GORM 默认事务?
GORM 对 Create、Save、Update、Delete、First(带 Update 或 Delete 条件时)等写操作,默认启用一个独立事务包裹。但注意:
-
Find、First(纯查询)、Count等只读操作 不开启事务,哪怕你配置了SkipDefaultTransaction: false -
Model(&u).Updates(...)这类更新,只要带 WHERE 条件,就会进默认事务;但AllowGlobalUpdate: true下的无条件更新(如db.Model(&User{}).Update("name", "x"))仍受默认事务保护 - 嵌套调用中,若外层已用
Session{SkipDefaultTransaction: true},内层不会“恢复”事务 —— 事务控制是会话级的,不继承
怎么确认某次操作是否在事务里?
最直接的办法是打开日志并观察 SQL 执行流:
db = db.Debug() // 或配置 Logger 输出 SQL
db.Create(&User{Name: "Alice"})
如果看到类似 BEGIN → INSERT ... → COMMIT 三行输出,说明走了默认事务;如果只有 INSERT ... 一行,大概率被 SkipDefaultTransaction: true 关掉了。
也可以加个钩子验证:
db.Callback().Create().Before("gorm:before_create").Register("check_tx", func(db *gorm.DB) {
tx := db.Statement.ConnPool.(*sql.DB)
// 实际无法直接判断是否在事务中,但可检查上下文或 Session 配置
if db.Statement.Settings.Get("skip_default_transaction") == true {
fmt.Println("⚠️ 默认事务已被跳过")
}
})
真正容易被忽略的点是:GORM 的“自动提交”依赖于它对操作类型的静态判断,而不是数据库连接状态。一旦你用 Session 改变了行为(比如关事务、换 context、设 DryRun),那个会话里的所有后续操作都会沿用该配置 —— 它不会因为下一行是 Create 就自动“恢复”事务。所以别假设“写了 Create 就一定有事务”,得看前面那句 Session() 干了什么。











