臭名昭著aTrust隔离计划1.臭名昭著aTrust隔离计划2.VPN原理与aTrust隔离网络实践3.docker-easyconnect到底做了什么4.TUN(tunnel-隧道-虚拟网卡)模式
个人项目
personal(个人展示)GitHub Actions 自动部署个人展示项目
基于Quartz搭建的个人博客1.Quartz个人博客使用教程2.使用 rsync 增量部署 Quartz 博客3.使用 GitHub Actions 自动部署 Quartz 博客5.域名绑定6.CDN加速7.Google 搜索收录排查:为什么关键词搜得到别人,却搜不到我的网站?Obsidian、双向链接与知识图谱
架构设计
分布式分布式事务RPC原理
旅行
行程攻略青岛三日游行程攻略
前端
NginxNginx配置与反向代理入门
Nodenpm与npx的区别
数据库
mysql01.MySQL基础语法与约束02.MySQL数据库类型03.MySQL多表查询04.MySQL 索引05.MySQL 事务06.MySQL 隔离级别07.MySQL MVCC08.MySQL 锁09.EXPLAIN10.慢查询与 SQL 优化
mysql面试题01.索引问题02.事务与隔离级别03.锁机制04.存储引擎与性能优化05.用户权限、字符集与备份恢复
mysql实战01.EXPLAIN实战02.测试环境菜单误删排查与恢复03.MySQL IN 详解
网络
网络安全1.无线监听网卡苹果M系列虚拟机安装Kali Linux教程3.kali怎么使用RTL8812AU4.kali基础命令
网络基础进程之间怎样通过网络传递数据
网址常用网址
中间键
esElasticsearch 入门
mq
RabbitMQRabbitMQ高级RabbitMQ基础
1. MQ 基础2. MQ 核心角色与代码对应3. MQ Consumer Group 与消费模式4. SOFAMQ 账户变更代码实践5. MQ 可靠性问题6. 消息幂等
redis缓存击穿、穿透与雪崩Redis 分布式锁Redis 缓存问题Redis 数据结构
AI
使用说明
第三方插件&技能简介Archify使用与安装指南geekgeekRun使用OfferFlow使用指南Ponytail使用指南Skills Manager使用指南
CodexCodex CLI与IDE区别及使用指南Codex Hook单独配置与提交通知Codex MCP安装与使用指南Codex第三方插件安装与使用指南
Agent开发0-学习路线1.LLM、Token、上下文窗口与模型参数2.大模型 API、请求参数与响应结构3.Spring AI ChatClient4.Prompt、System Prompt、Prompt 模板6.结构化输出、JSON Schema7.SSE 流式响应8.会话 ID、聊天记录、Redis9.超时、重试、限流、降级10.*完成可运行聊天接口11.Function Calling、Tool Calling 原理12.用 Java 方法定义 Tool13.工具参数设计和校验14.实现订单查询工具15.实现物流查询工具16.实现库存查询工具17.多个工具自动选择18.工具异常、超时、重试
diy主机
主机零件学习1.CPU2.主板3.内存4.显卡5.硬盘6.散热7.风扇8.机箱热门型号价格表
捡垃圾diy-13600kf+5060diy-14600kf+5070
English高频词汇英文基础语法简单句子音标
GitGit常用命令Sublime Merge 使用指南
Java
工具库Caffeine本地缓存Reactor教程
工作实践如何使用@Async线程池使用与排查线上服务 CPU 使用率过高排查压测下RPC接口大量失败排查记录
基础1.HashMap2.ConcurrentHashMap3.HashSet4.final5.static6.volatile7.AtomicInteger8.String动态代理反射锁与线程同步线程注解
面试题1.HashMap 存储在方法内是线程安全的吗2.ArrayList参数传递与引用重新赋值3.== 和 equals 的区别、hashcode4.重载和重写的区别5.String、StringBuffer、StringBuilder区别及使用场景6.接口和抽象类7.ArrayList 与 LinkedList 的区别和扩容机制8.HashMap 和 Hashtable 的区别及底层实现9.Object 和 Objects 的区别10.ThreadLocal的原理和使用场景11.字节码是什么及作用12.Java类加载器有哪些13.双亲委派模型14.Java异常体系15.GC是什么16.GC如何判断对象可以被回收
面试总结0.HR面试模板1.面试一2.面试二3.面试三笔试
网络与 IO(黑马程序员-纯理论)01-网络编程基础02-IO模型与零拷贝03-BIO04-NIO基础与缓冲区05-NIO通道与选择器06-AIO
juc00-目录01-进程与线程02-线程同步与通信03-并发设计模式04-Java内存模型05-CAS与原子类06-ThreadLocal07-阻塞队列08-线程池使用与原理09-任务调度10-ForkJoin与享元模式11-AQS12-ReentrantLock13-读写锁14-并发协作工具15-ConcurrentHashMap16-CopyOnWrite与同步容器17-SkipListMap18-非阻塞队列
juc(工作常见)Java并发编程三个重点拆解
jvm1. JVM运行时数据区
Spring1.Spring IoC、DI与构造方法注入CGLIB、Byte Buddy 与 Spring AOPSpring 常用注解详解Spring Bean 生命周期
Spring MVC异常处理机制
linux常用命令
mac快捷键终端常用操作
python
基础01.Python是什么,为什么要安装Python02.Python项目中.venv是什么03.Python基础语法
基础实战sql替换
HTTPXHRMS员工详情接口调用(Python HTTPX)
windowswindows激活

一、💡 一句话理解

核心结论

分布式事务,就是一次业务跨越多个独立事务资源后,协调各自的数据修改,使最终结果符合业务约定。
本地事务管自己,分布式事务协调大家;超时不等于没执行,补偿不等于数据库回滚。

本文沿用官方资料的理解依赖:先理解本地事务,再理解跨资源的协调,最后比较两阶段提交和业务补偿。以“下单、扣库存”为主线,不绑定具体中间件版本;资料查阅日期为 2026-10-20。

二、🧭 理论:它是什么

2.1 先理解本地事务

用户买一部手机,需要创建订单并扣减库存。如果两张表由同一个数据库事务管理,就可以一起提交;提交前发生错误,可以一起回滚。前置知识见 05.MySQL 事务。

开始本地事务 → 创建订单 → 扣减库存 → 一起提交
                     任一步失败 → 一起回滚

“本地”描述的是事务管理边界,不只是机器数量。分布式数据库也可能对应用提供统一事务接口。

2.2 拆开数据库后,问题发生在哪里

拆成订单服务和库存服务后,各自管理自己的数据库:

场景角色对应技术对象自己能保证什么
订单服务订单数据库的本地事务自己的订单修改提交或回滚
库存服务库存数据库的本地事务自己的库存修改提交或回滚
一次下单跨资源的全局业务需要额外协调各个步骤

如果订单已经提交,扣库存却失败,订单数据库不会自动撤销已经提交的订单。普通本地事务也不会因为调用了远程接口,就自动把对方数据库纳入自己的事务。

这里使用的是真实业务示例。核心判断是“是否跨越独立事务边界”,不能只凭调用了几个方法或服务判断。

三、⚙️ 理论:它是怎么工作的

3.1 最难的是结果未知

订单服务发出扣库存请求
→ 库存服务扣减成功并提交
→ 返回结果时网络断开
→ 订单服务只看到了超时

超时只能说明没有及时收到结果,不能证明没有执行。直接重试可能重复扣库存,直接取消又可能遗留已扣的库存。

因此协调机制需要处理三件事:明确结果、避免重复生效,以及故障恢复。生产实现通常需要持久化状态、查询或重试、幂等和异常对账。

3.2 两阶段提交:先准备,再统一决策

两阶段提交(2PC)把提交分成两个阶段:

  1. 准备阶段:协调者询问各参与方能否提交;参与方准备好后,保存必要状态,等待最终决定。
  2. 决策阶段:所有参与方准备成功,协调者决定提交;如果不能满足提交条件,则决定回滚,并推动参与方完成决定。

准备不是单纯检查“服务在线”。参与方必须能够在之后兑现提交或回滚决定,通常会保留锁等资源。

使用边界

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 锁。

七、🔗 官方资料

以下是实际阅读的一手资料;示例代码为本文自行编写的内存演示,不是官方生产示例。