database/sql 事务错误回滚与提交判断的清理清单
线上写订单、库存或账务数据时,事务代码最容易漏掉的不是 SQL 本身,而是错误路径:开启失败没有继续判断、执行失败没有回滚、Commit 返回错误却被当成“肯定没写进去”。一份可靠的清理清单应该把这三种状态分开处理:事务尚未开始、事务明确失败、提交结果未知。
- 拿到
*sql.Tx后立即登记defer tx.Rollback(),让未提交路径自动收口。 - 业务操作失败先保留原始错误;回滚失败作为附加信息记录,不要覆盖根因。
Commit失败不等于数据库一定没有变化,涉及重试时要先确认幂等键和结果查询方案。
先把事务状态和清理责任分开
database/sql 的事务必须以 Commit 或 Rollback 结束。开启阶段的错误不属于“回滚失败”,因为此时通常还没有可供调用的事务对象;进入事务后的错误才需要走回滚路径。用 BeginTx 接收请求上下文,能让取消信号参与事务生命周期,但也意味着上下文取消后不能再把提交结果想当然地当成成功或失败。

实际代码可以把兜底回滚放在拿到事务之后。提交成功后,后续的 Rollback 会返回事务已结束一类的错误,因此不要把这个 defer 当成第二次提交;它只是覆盖提前返回和异常分支。
// saveOrder 只展示事务边界;具体驱动和表结构由调用方提供。
func saveOrder(ctx context.Context, db *sql.DB, orderID int64) error {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
// 开启失败时没有可清理的 Tx,直接返回原始错误。
return fmt.Errorf("begin transaction: %w", err)
}
defer tx.Rollback() // 未提交路径自动回滚;提交后的 ErrTxDone 不改变成功结果。
if _, err = tx.ExecContext(ctx,
"UPDATE orders SET status = ? WHERE id = ?", "paid", orderID); err != nil {
// 业务错误优先保留,回滚只负责释放事务状态。
return fmt.Errorf("update order: %w", err)
}
if err = tx.Commit(); err != nil {
// Commit 失败可能处于未知状态,不能直接当作“未提交”重放写入。
return fmt.Errorf("commit transaction: %w", err)
}
return nil
}
执行失败时,回滚错误怎么处理
执行语句、扫描结果或业务校验失败,都应该先让 defer 触发回滚,再把原始错误返回。若需要显式记录回滚结果,可以把回滚错误作为日志字段或包装错误的附加信息;不要用“回滚失败”覆盖真正导致订单更新失败的 SQL 错误。只有在回滚本身影响后续补偿判断时,才把两者一起上报。
查询类事务还要把 Rows.Close 和 Rows.Err 纳入检查。行迭代中途发生网络或驱动错误,不能因为已经读到几行数据就继续提交。资源清理和事务清理是两条线:关闭 rows 释放结果集,回滚或提交结束事务。
| 位置 | 需要判断 | 处理动作 |
|---|---|---|
BeginTx | 返回的 error | 直接返回,不调用 tx 方法 |
| Exec/Scan/Rows.Err | 业务或驱动错误 | 保留原错误,走回滚兜底 |
Rollback | 回滚是否也失败 | 记录附加错误,标记需要人工或补偿处理 |
Commit | 提交返回值 | 失败按未知状态处理,避免无条件重试 |

Commit 失败不要直接按失败重放
Commit 返回错误的危险点在于:调用方可能已经把请求发给数据库,但客户端没有可靠收到最终结果。此时最稳妥的清理清单是记录事务标识、业务幂等键、数据库错误和请求上下文状态,再根据业务查询确认最终状态。支付、库存扣减这类不可重复写入,应该让重试依赖幂等键或补偿任务;普通的网络错误不能自动推出“数据库没有变化”。
如果使用 BeginTx 的 context 被取消,标准库会尝试回滚,提交也可能返回错误。代码层面仍要把这个结果交给上层,而不是在 defer 中偷偷重试。重试策略属于业务协议,必须知道重复执行会不会产生第二条订单、第二次扣减或状态覆盖。
发布前的事务清理清单
- 开启失败是否立即返回,且没有对 nil 的
tx调用方法? - 拿到事务后是否立刻登记回滚兜底?
- 每个写操作、扫描操作和
Rows.Err是否都检查? - 回滚失败是否作为附加证据记录,而不是丢掉原始错误?
- 提交失败是否按未知状态进入幂等查询或补偿流程?
- 日志里是否有业务幂等键、事务阶段和驱动错误,且没有输出敏感参数?
相关问题
defer tx.Rollback 会不会影响成功提交?
正常提交后事务已经结束,defer 中的回滚只会得到事务完成类错误;它不会撤销已经成功提交的结果。
Commit 报错后能不能马上再调用 Rollback?
可以把回滚作为尽力清理,但不能据此判断写入一定未发生。真正的重试前,先用幂等键或业务查询确认状态。
为什么不只在错误分支手写 Rollback?
分支越多越容易遗漏。defer 提供统一兜底,显式回滚只在需要组合错误信息或提前结束时使用。
官方资料:https://pkg.go.dev/database/sql;事务操作说明:https://go.dev/doc/database/execute-transactions。这些页面可用于核对 Tx 的结束语义和 BeginTx 的上下文行为。
archive/zip 创建目录条目并保持文件权限的方法
- 上一篇
- archive/zip 创建目录条目并保持文件权限的方法
- 下一篇
- sql.Stmt 复用预编译语句并绑定连接的实践示例
-
- Golang · Go教程 | 12分钟前 |
- text/template 自定义函数并处理空值的模板方案
- 116浏览 收藏
-
- Golang · Go教程 | 12分钟前 | Go regexp 命名捕获组 SubexpNames
- regexp.SubexpNames 映射命名捕获组结果的方法
- 170浏览 收藏
-
- Golang · Go教程 | 14分钟前 | Go教程 · html/template · html/template href 上下文转义 动态链接属性 Go模板安全
- html/template 安全输出动态链接属性的设计要点
- 477浏览 收藏
-
- Golang · Go教程 | 16分钟前 | 标准库 · Go教程 · Go 位运算 位字段 math/bits.RotateLeft RotateLeft32 RotateLeft64
- math/bits.RotateLeft 处理定长位字段的参数用法
- 419浏览 收藏
-
- Golang · Go教程 | 19分钟前 | 随机数 · Go教程 · 安全随机数 crypto/rand.Int rand.Reader big.NewInt Go随机数
- crypto/rand.Int 生成范围内随机数的实现方式
- 408浏览 收藏
-
- Golang · Go教程 | 20分钟前 | shuffle Go 切片 math/rand/v2 随机状态
- math/rand/v2 Shuffle 打乱切片并隔离全局状态的方法
- 430浏览 收藏
-
- Golang · Go教程 | 22分钟前 | go · archive/tar filepath.IsLocal ErrInsecurePath tarinsecurepath
- archive/tar 解包时校验相对路径的安全实现
- 136浏览 收藏
-
- Golang · Go教程 | 25分钟前 | Go教程 · reset Multistream gzip.Reader Go压缩读取 多成员压缩流
- gzip.Reader 处理多成员压缩流的读取方案
- 463浏览 收藏
-
- Golang · Go教程 | 27分钟前 | Go教程 · database/sql · database/sql Go数据库 sql.Stmt PrepareContext Conn.PrepareContext
- sql.Stmt 复用预编译语句并绑定连接的实践示例
- 123浏览 收藏
-
- Golang · Go教程 | 28分钟前 | Go 文件权限 archive/zip 目录条目
- archive/zip 创建目录条目并保持文件权限的方法
- 292浏览 收藏
-
- Golang · Go教程 | 32分钟前 | 标准库 · 数据库 · Go教程 · 工程实践 · Go SCAN 空字符串 database/sql Value sql.NullString
- sql.NullString 区分 NULL 与空字符串的处理方案
- 281浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 999次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 1002次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 1122次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 1097次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 941次使用
-
- Go map 并发写 panic 怎么办:从共享 map 到可控写入路径
- 2026-06-30 123浏览
-
- Go代码规范错误处理示例经验总结
- 2022-12-23 278浏览
-
- go语言中的defer关键字
- 2023-02-17 150浏览
-
- Golang中Interface接口的三个特性
- 2023-01-07 394浏览
-
- go语言中函数与方法介绍
- 2023-01-07 297浏览

