一、💡 一句话理解
核心结论
分布式事务,就是一次业务跨越多个独立事务资源后,协调各自的数据修改,使最终结果符合业务约定。
本地事务管自己,分布式事务协调大家;超时不等于没执行,补偿不等于数据库回滚。
本文沿用官方资料的理解依赖:先理解本地事务,再理解跨资源的协调,最后比较两阶段提交和业务补偿。以“下单、扣库存”为主线,不绑定具体中间件版本;资料查阅日期为 2026-10-20。
二、🧭 理论:它是什么
2.1 先理解本地事务
用户买一部手机,需要创建订单并扣减库存。如果两张表由同一个数据库事务管理,就可以一起提交;提交前发生错误,可以一起回滚。前置知识见 05.MySQL 事务。
开始本地事务 → 创建订单 → 扣减库存 → 一起提交
任一步失败 → 一起回滚“本地”描述的是事务管理边界,不只是机器数量。分布式数据库也可能对应用提供统一事务接口。
2.2 拆开数据库后,问题发生在哪里
拆成订单服务和库存服务后,各自管理自己的数据库:
| 场景角色 | 对应技术对象 | 自己能保证什么 |
|---|---|---|
| 订单服务 | 订单数据库的本地事务 | 自己的订单修改提交或回滚 |
| 库存服务 | 库存数据库的本地事务 | 自己的库存修改提交或回滚 |
| 一次下单 | 跨资源的全局业务 | 需要额外协调各个步骤 |
如果订单已经提交,扣库存却失败,订单数据库不会自动撤销已经提交的订单。普通本地事务也不会因为调用了远程接口,就自动把对方数据库纳入自己的事务。
这里使用的是真实业务示例。核心判断是“是否跨越独立事务边界”,不能只凭调用了几个方法或服务判断。
三、⚙️ 理论:它是怎么工作的
3.1 最难的是结果未知
订单服务发出扣库存请求
→ 库存服务扣减成功并提交
→ 返回结果时网络断开
→ 订单服务只看到了超时超时只能说明没有及时收到结果,不能证明没有执行。直接重试可能重复扣库存,直接取消又可能遗留已扣的库存。
因此协调机制需要处理三件事:明确结果、避免重复生效,以及故障恢复。生产实现通常需要持久化状态、查询或重试、幂等和异常对账。
3.2 两阶段提交:先准备,再统一决策
两阶段提交(2PC)把提交分成两个阶段:
- 准备阶段:协调者询问各参与方能否提交;参与方准备好后,保存必要状态,等待最终决定。
- 决策阶段:所有参与方准备成功,协调者决定提交;如果不能满足提交条件,则决定回滚,并推动参与方完成决定。
准备不是单纯检查“服务在线”。参与方必须能够在之后兑现提交或回滚决定,通常会保留锁等资源。
使用边界
2PC 协调的是原子提交,不应直接等同于完整的跨库 ACID 或严格串行化。隔离能力还取决于参与资源和实现。准备后的故障可能造成等待,恢复机制与资源占用都需要考虑。
3.3 TCC:先预留,再确认或释放
TCC 把业务操作拆成三个接口:
| 阶段 | 含义 | 库存示例 |
|---|---|---|
| Try | 检查并预留资源 | 把 1 件可售库存转成预留库存 |
| Confirm | 确认业务完成 | 消耗这 1 件预留库存 |
| Cancel | 撤销预留 | 把这 1 件预留库存还给可售库存 |
业务需要自行设计预留状态、确认与取消逻辑,并处理重复调用、未完成 Try 就收到 Cancel 等异常。它不是把同一条扣库存 SQL 执行三遍。
3.4 Saga:先提交,失败后做业务补偿
Saga 将长业务拆成多个本地事务。每一步先提交,后续失败时,对已经完成且需要撤销的步骤执行补偿。
创建待确认订单,提交
→ 扣库存明确失败
→ 执行补偿:把订单改成已取消,提交补偿是新的业务操作。 原订单并没有从历史上消失,只是状态变成了“已取消”。已发送的通知、已发生的物流等副作用,也未必能完全逆转。
Saga 不自动提供跨步骤隔离,其他请求可能看到中间状态;补偿也可能失败,需要记录进度并重试或人工处理。
3.5 Seata AT:根据回滚日志恢复
Seata AT 的第一阶段,将业务修改和回滚日志放在同一个本地事务中提交,并配合全局锁。全局成功后清理日志;全局失败时,根据日志校验并恢复数据。
它依赖数据库、本地事务、数据源代理和 SQL 等支持条件。普通读取不因此自动获得全局串行化隔离,不能理解为“所有数据库同时提交”。
3.6 放在一起怎么区分
| 方案 | 核心思路 | 主要代价或限制 |
|---|---|---|
| 2PC | 准备后统一决定提交或回滚 | 资源占用、故障等待 |
| TCC | 预留资源,再确认或释放 | 业务接口和异常处理复杂 |
| Saga | 本地提交后,通过业务补偿收敛 | 中间状态可见,补偿可能失败 |
| Seata AT | 本地提交并记录日志,失败时恢复 | 支持条件、锁、隔离与数据校验限制 |
选择时先问:业务能否接受暂时不一致?是否能预留?是否能补偿?跨越范围是否真的有必要?如果可以把关键修改收敛到一个本地事务,通常更容易保证正确性。
四、🚀 实践:从准备到验证
4.1 前置准备
本实践只模拟 Saga 的业务状态变化,需要 Python 3,无额外依赖、账号或数据库配置。
把下方代码保存为本机任意学习目录中的 demo.py。它只修改内存,不连接数据库或远程服务;进程退出后数据不保留。
4.2 可以拿来干什么
用于观察:库存不足时,已经创建的订单如何通过“取消订单”这一步补偿,回到业务认可的结果。
输入是订单编号和初始库存。订单、库存都由下方程序初始化;输出是业务结果、订单状态和剩余库存。适合入门理解补偿,不适合拿来实现生产环境事务。
4.3 完整实践:代码、启动和验证
要解决的问题:模拟订单已提交,但库存明确拒绝扣减时,如何取消订单。
# 用内存模拟两个服务的数据;这不是真实数据库或分布式事务框架。
orders = {}
stock = {"手机": 0}
def place_order(order_id):
# 第一步:模拟订单服务完成了本地提交。
orders[order_id] = "待确认"
# 第二步:模拟库存服务明确返回库存不足。
# 这里没有模拟网络超时;不能把超时也当成库存未扣成功。
if stock["手机"] < 1:
# 补偿是新的业务操作:取消已创建的订单,而不是数据库回滚。
orders[order_id] = "已取消"
return "库存不足,已补偿取消订单"
# 库存充足:模拟库存扣减成功,然后确认订单。
stock["手机"] -= 1
orders[order_id] = "已确认"
return "下单成功"
print(place_order("ORDER-001"))
print(orders)
print(stock)在文件所在目录运行:
# 启动程序;程序自己发起一次模拟下单,不需要 HTTP 请求。
python3 demo.py初始库存为 0 时,预期输出:
库存不足,已补偿取消订单
{'ORDER-001': '已取消'}
{'手机': 0}把初始库存改为 1 后重新启动,预期输出:
下单成功
{'ORDER-001': '已确认'}
{'手机': 0}输入、过程、结果:ORDER-001 发起一次下单,程序先创建待确认订单,再检查库存,最终确认或补偿取消订单。
| 问题 | 排查方法 |
|---|---|
找不到 python3 | 先安装 Python 3,并检查命令是否可用 |
提示找不到 demo.py | 切换到保存该文件的目录,或传入文件绝对路径 |
| 缩进错误 | 保留 Python 的空格缩进,不把 Markdown 围栏复制进文件 |
| 每次运行数据都重置 | 这是内存演示的预期行为,没有持久化 |
示例实际做到了什么
本例只演示成功和明确失败时的业务补偿。它没有实现网络通信、持久化、崩溃恢复、并发控制、幂等或补偿重试。真实 Saga 需要补齐这些机制,不能把内存示例视为可靠事务实现。
五、🎤 面试时怎么流利地说
5.1 回答路线
是什么 → 解决什么问题 → 核心机制 → 方案差异 → 使用边界。
从“订单成功、库存失败”出发,再补充“库存已成功,但响应丢失”的情形,就能自然引出协调、幂等和恢复。
5.2 30 秒标准回答
分布式事务解决的是,一次业务跨越多个独立事务资源后,如何协调各自的数据修改。例如创建订单成功,但扣库存失败,就需要恢复业务一致的结果。常见方案有两阶段提交、TCC 和 Saga,分别通过统一提交决策、资源预留和业务补偿来处理。选择时要考虑一致性要求、性能和能否补偿,本地事务不会自动覆盖远程服务。
5.3 1—2 分钟展开回答
我会先判断业务是否跨越了独立事务边界。如果订单和库存能放进同一个本地事务,可以直接利用数据库事务;如果分别由两个服务的数据库管理,就需要额外协调。
难点不仅是明确失败,还有结果未知。例如库存已经扣减,但响应丢了,调用方看到超时,并不能直接认定失败,也不能盲目重试。
方案上,2PC 先让参与方准备,再统一决定提交或回滚,但会带来资源占用和故障等待。TCC 在业务层先预留资源,再确认或取消,需要自己实现相应接口。Saga 让各步骤先提交,后续失败时再补偿,适合能接受中间状态、可以补偿的业务。
实际设计还要考虑持久化、幂等、重试和对账。补偿不是数据库回滚,也可能失败。因此我会先明确业务不变量和恢复路径,再选择具体方案,而不是只加一个注解就认为问题解决了。
5.4 高频追问与短答
远程调用超时,可以直接重试吗?
不能默认安全。对方可能已执行成功,通常需要稳定的业务标识、幂等处理,以及结果查询或可靠重试机制。
Saga 的补偿和数据库回滚有什么区别?
数据库回滚撤销事务内未提交的修改;Saga 补偿针对已经提交的业务结果,执行新的业务操作。补偿不保证抹除所有副作用,也可能失败。
最终一致性是不是过一会儿就自然一致?
不是。它依赖明确的重试、补偿、持久化恢复等机制,以及业务最终能够推进的条件;失败没有被持续处理,就可能一直不一致。
六、📌 总结
- 本地事务协调自己管理范围内的修改;跨独立事务资源需要额外协调。
- 超时代表未及时收到结果,不能证明对方没有执行。
- 2PC 先准备再决策,TCC 先预留再确认,Saga 先执行再补偿。
- 业务补偿是新操作,不等于撤销历史,也不等于数据库回滚。
- 先明确一致性要求、业务不变量和故障恢复路径,再选方案。
记忆句:本地管自己,全局要协调;超时查结果,补偿有代价。
相关笔记:05.MySQL 事务、06.MySQL 隔离级别、08.MySQL 锁。
七、🔗 官方资料
以下是实际阅读的一手资料;示例代码为本文自行编写的内存演示,不是官方生产示例。
- PostgreSQL:Transactions:本地事务、提交与回滚。
- PostgreSQL:Two-Phase Transactions:两阶段事务与外部事务管理器。
- Apache Seata:What Is Seata?:AT 的阶段、日志、全局锁和隔离,以及 TCC 的阶段职责。
- Apache Seata:Saga 模式:正向操作、补偿与隔离限制。