GORM 中 SavePoint 需显式调用 tx.SavePoint("sp_name") 和 tx.RollbackTo("sp_name"),仅 v1.24+ 支持且须在事务内使用;命名严格匹配、不可混用原生 SQL;每次 RollbackTo 后必须检查 error。

SavePoint 在 GORM 里不是自动可用的 API
GORM 没有 tx.RollbackTo("sp_name") 这样的公开方法,硬调会编译失败或 panic。它底层用的是数据库 SAVEPOINT 机制,但封装层级较高,不暴露原生控制权。你看到的 “嵌套事务” 实际是 tx.Begin() 内部自动设了一个匿名保存点,而不是让你自由命名和跳转。
正确创建并回滚到命名 SavePoint 的唯一方式
必须显式调用 tx.SavePoint("sp_name"),再配合 tx.RollbackTo("sp_name") —— 这两个方法从 GORM v1.24 起才稳定支持,且只在已有事务内有效(即不能对 db 直接调用)。
-
tx.SavePoint("sp_config")必须在你想“锚定”的位置之前执行,比如主记录插入后、子表操作前 -
tx.RollbackTo("sp_config")只撤销该保存点之后的变更,tx仍处于活跃状态,可继续执行其他语句 - 名字必须完全一致,大小写敏感;重复使用同一名字会覆盖前一个保存点
- MySQL 和 PostgreSQL 行为一致,但 PostgreSQL 允许对不存在的保存点执行
ROLLBACK TO SAVEPOINT(返回 warning),MySQL 则报错
混用原生 SQL 和 GORM SavePoint 会出问题
别一边用 tx.SavePoint("sp1"),一边又写 tx.Exec("SAVEPOINT sp1")。GORM 的 SavePoint 是封装过的,内部做了连接复用和上下文绑定;而原生 EXEC 发送的命令可能被 ORM 缓存层绕过,导致 RollbackTo 找不到对应节点,报 "no such savepoint"。
- 所有保存点操作必须统一走 GORM 方法:
SavePoint/RollbackTo - 避免在同一个事务中交叉使用
tx.Create()和tx.Exec("INSERT..."),特别是带 RETURNING 或 LAST_INSERT_ID() 的场景,容易因驱动差异丢失上下文 - 如果必须用原生 SQL(比如触发器、复杂 CTE),优先用
tx.Raw().Exec(),它比裸tx.Exec()更贴近 GORM 事务生命周期
RollbackTo 后忘记检查 error 是常见静默故障点
tx.RollbackTo("sp_name") 失败时不会 panic,但返回 error。若忽略这个 error(比如只写 _ = tx.RollbackTo("sp")),后续继续操作可能基于错误状态写入脏数据。
- 每次
RollbackTo后都应判断:if err != nil { return err }或降级为整体回滚tx.Rollback() - 不要依赖
defer tx.Rollback()来兜底——它会在成功路径上误触发,清空本该提交的数据 - 外层事务最终是否
Commit(),必须由业务逻辑显式决定:所有子流程 error 都为 nil 才能 commit


















