savepoint 是事务内的标记点而非嵌套事务,仅支持回滚到指定保存点(rollback to savepoint),不支持提交;命名需唯一且具业务语义,须显式调用 rollbackto 才生效,混用原生 sql 会导致回滚失败。

SavePoint 不是嵌套事务,只是事务内的标记点
GORM 的 tx.SavePoint("sp_name") 从不开启新事务,它只是在当前事务里建一个 PostgreSQL/MySQL 支持的保存点(SAVEPOINT)。调用后返回的 *gorm.DB 实例和外层 tx 共享同一底层 *sql.Tx 连接,所有后续操作仍属于同一个事务上下文。
常见错误现象:在 db.Transaction() 回调里调 tx.SavePoint(),再调 inner.Commit() —— 这个 Commit() 被 GORM 忽略,因为保存点不支持“提交”,只支持回滚到它。
-
tx.RollbackTo("sp_name")才真正触发ROLLBACK TO SAVEPOINT sp_name - 若外层已
Commit()或Rollback(),再调RollbackTo()会报错(如 PostgreSQL 的current transaction is aborted) - 全局配置了
DisableNestedTransaction: true时,SavePoint()会静默失效,且无 warning
命名必须唯一,且要体现业务语义
重复调用 tx.SavePoint("sp1") 会覆盖前一个同名保存点,导致 RollbackTo("sp1") 跳过中间状态。尤其在并发或循环中动态生成名字(如 "sp_" + uuid.NewString()[:8])时,仍有碰撞风险。
推荐按业务子单元命名,而不是按操作顺序或时间戳:
- ✅
tx.SavePoint("sp_profile_create") - ✅
tx.SavePoint("sp_address_upsert") - ❌
tx.SavePoint("sp_1")、tx.SavePoint("sp_" + time.Now().String())
命名带业务含义,能快速定位回滚影响范围,也方便日志追踪和 debug。
局部失败需手动 RollbackTo,不能依赖自动恢复
保存点的价值在于「主流程不可逆,子操作可选失败」。例如:用户记录已插入,但其关联的 notification_setting 表因约束失败,你只想撤回后者,不影响前者。
关键动作必须显式完成:
- 先
tx.SavePoint("sp_notification") - 再执行可能失败的操作(如
tx.Create(&setting)) - 失败时立刻
tx.RollbackTo("sp_notification"),**不要等外层事务结束** - 之后仍可继续用同一
tx执行其他操作(如插入 role)
注意:RollbackTo() 返回 error 时,通常意味着保存点名拼错、已被释放,或事务已处于 abort 状态——这时整个事务已无法继续,应尽早返回错误并让外层处理回滚。
别混用 GORM SavePoint 和原生 SQL SAVEPOINT
GORM 的 SavePoint() 和 RollbackTo() 是封装好的方法,底层会发对应 SQL;而直接 tx.Exec("SAVEPOINT sp1") 后,GORM 不知道这个点存在,调 tx.RollbackTo("sp1") 可能报 no such savepoint。
统一使用 GORM 提供的接口,避免交叉调用:
- ✅ 全程用
tx.SavePoint("sp_x")+tx.RollbackTo("sp_x") - ❌ 混用
tx.Exec("SAVEPOINT sp1")和tx.RollbackTo("sp1") - ❌ 在 GORM
SavePoint()后,又用tx.Exec("ROLLBACK TO SAVEPOINT sp1")
保存点不是语法糖,它是事务内精确控制数据边界的真实机制。名字写错、时机不对、混用接口,都会让回滚失效,最终留下脏数据。











