db.transaction未提交因未显式调用tx.commit()且无自动提交机制,defer tx.rollback()仅兜底panic;aftercommit钩子须在事务内注册且绑定原实例;嵌套事务实为savepoint,需统一api操作;http等i/o严禁入事务。

db.Transaction 为什么没报错却没提交?
因为 db.Transaction 默认只执行你传进去的函数,不检查返回值、也不捕获 panic。如果函数里忘了 tx.Commit(),又没显式 tx.Rollback(),事务在连接归还给池时就静默丢弃了——数据既没写进去,也没回滚,连错误都没有。
- 必须在事务函数开头加
defer tx.Rollback()做兜底 - 成功路径上要显式调用
tx.Commit(),不能靠函数返回nil自动提交 - 别在中间
return,改用命名返回值或统一出口:比如err := ...; if err != nil { return err } - 若函数 panic,
defer tx.Rollback()会生效;但若只是逻辑错误(如条件跳过 commit),那就真丢了
AfterCommit 钩子为啥不触发?
tx.AfterCommit() 只在 tx.Commit() 成功后才调用,且严格绑定当前 *gorm.DB 实例——它不跨 session、不跨 goroutine、不继承到新 db 对象。
- 钩子注册必须写在
db.Transaction函数体内,且要在tx创建后立刻调用,比如tx.AfterCommit(func() { ... }) - 如果在事务里用了
tx.Session(...)或tx.WithContext(...)新建了*gorm.DB,钩子就失效了 - 钩子里别做 HTTP 请求、远程日志等耗时操作;建议发消息到
chan或投递异步任务 - 多个
AfterCommit按注册顺序执行,但彼此无原子性,一个 panic 不影响另一个
嵌套事务其实是 savepoint,怎么安全用?
GORM 没有真正的嵌套事务,db.Transaction 套 db.Transaction 实际是用 SAVEPOINT 模拟的。外层 rollback 会让所有 savepoint 失效,但你的代码若还去 ROLLBACK TO SAVEPOINT,就会报 ERROR: no such savepoint。
- 统一用
tx.SavePoint("sp_name")和tx.RollbackTo("sp_name"),别混用原生 SQL 的SAVEPOINT - 每次
RollbackTo后必须检查错误,失败则应降级为整体tx.Rollback() - 避免跨函数传递 savepoint 名,容易命名冲突;建议用
uuid.NewString()动态生成 - 除非业务强依赖局部回滚,否则优先拆成独立事务 + 幂等重试,比 savepoint 更可控
HTTP 调用能放进事务里吗?
不能。HTTP 请求超时、重试、网络分区都会让事务长时间挂起,拖垮连接池,甚至导致整个服务不可用。GORM 本身不提供事务级超时控制,context.WithTimeout 传给 db.Transaction 只能中断函数执行,不会自动 rollback。
- HTTP 调用必须移出事务函数体,在
AfterCommit或事务外异步发起 - 若需保证“数据库写入”和“外部通知”最终一致,用本地消息表 + 定时补偿,而不是强一致性事务
- 不要在事务中调用任何可能阻塞的 I/O,包括文件读写、RPC、缓存 Set 等
- 特别注意:Gin 中的
c.Request.Context()和事务 context 是两回事,别误传
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











