分布式事务解决方案:最佳实践(零基础入门教程)
开篇:什么是分布式事务?为什么要学?

在我们开发一个简单的网站时,比如用户下单购买商品,通常会涉及多个操作:
- 扣除库存
- 记录订单
- 扣减用户账户余额
这些操作一般都发生在同一个数据库中。如果我们使用传统的事务机制(如 MySQL 的 BEGIN ... COMMIT/ROLLBACK),就可以保证这三个操作要么全部成功,要么都不执行。
但随着业务增长,我们可能会将系统拆分成多个服务,例如:
- 用户服务
- 商品服务
- 订单服务
- 支付服务
每个服务都有自己的数据库。此时,要完成一次“下单”操作,就涉及到不同数据库之间的数据修改,这种场景下的事务问题,就是我们常说的 分布式事务问题。
环境准备:搭建开发环境

为了完成后续示例,你需要准备以下工具和软件:
1. Java 运行环境(JDK 1.8 或更高)
下载地址:https://www.oracle.com/java/technologies/javase-downloads.html
安装完成后,在终端输入命令查看版本:
java -version
应该看到类似以下输出:
openjdk version "17.0.5" 2022-10-18
2. Maven(构建工具)
下载地址:https://maven.apache.org/download.cgi
安装后执行:
mvn -v
看到版本信息说明安装成功。
3. Spring Boot + Seata(分布式事务中间件)
我们将使用 Spring Boot + MyBatis + Seata 来演示分布式事务。
Seata 是阿里巴巴开源的一个分布式事务框架,对新手很友好。
你也可以使用其他方案如 XA、TCC、Saga 等,但在本教程中我们先从最易上手的 Seata 入手。
安装 Seata Server
访问 GitHub 下载最新版:
👉 https://github.com/seata/seata/releases
解压后,在 conf/start.sh 启动 Seata 服务端:
cd seata-server
sh start.sh
启动成功后会显示如下信息:
started @ 1234ms
表示 Seata 正在运行,默认监听端口为 8091。
核心概念:用最简单的语言解释专业术语


📌 什么是事务?
事务是一个操作单元,要么全部执行成功,要么全部失败回滚。比如银行转账,不能只扣钱不加钱。
📌 本地事务 vs 分布式事务
- 本地事务:单个数据库内部的事务,由数据库本身支持,简单可靠。
- 分布式事务:多个数据库之间的操作需要保持一致性,难度高,需要借助第三方框架处理。
📌 CAP 理论简述
CAP 是理解分布式系统的关键:
- C:Consistency(一致性)
- A:Availability(可用性)
- P:Partition tolerance(分区容忍性)
这三者最多只能同时满足两个。所以我们在设计系统时,要权衡取舍。
对于分布式事务,我们通常更关注 一致性 和 可用性。
📌 常见的分布式事务方案
| 名称 | 简介 | 是否适合初学者 |
|---|---|---|
| 两阶段提交(2PC) | 经典协议,协调器负责决策,存在单点故障 | ❌ |
| TCC | Try - Confirm - Cancel 模式,手动控制补偿逻辑 | ✅ |
| Saga | 长时间运行的事务,通过逆向操作补偿 | ⚠️ |
| 最大努力通知 | 通过消息队列异步通知下游系统 | ✅ |
| Seata | 国内主流开源框架,支持 AT 模式,自动管理事务日志 | ✅✅ |
实战项目:一步步实现 Seata 分布式事务

目标:模拟用户下单购买商品,涉及三个服务:
- 库存服务(Inventory Service)
- 订单服务(Order Service)
- 账户服务(Account Service)
我们要确保下单过程中,所有操作一致成功或失败。
第一步:创建 Spring Boot 多模块项目结构
使用 IDEA 创建一个 Maven 多模块项目,包含三个子模块:
inventory-serviceorder-serviceaccount-service
主 pom.xml 中引入公共依赖:
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
第二步:配置 Seata 客户端
在每个服务的 application.yml 中添加:
seata:
enabled: true
application-id: inventory-service
tx-service-group: my_tx_group
service:
vgroup-mapping:
my_tx_group: default
grouplist:
default: 127.0.0.1:8091
application-id表示当前服务名称tx-service-group是组名,用于集群分组grouplist指向刚刚启动的 Seata 服务地址
第三步:初始化数据库表结构(以库存为例)
每个服务都有自己独立的数据库,比如:
库存数据库(inventory_db):
CREATE TABLE t_inventory (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
product_code VARCHAR(50),
stock INT
);
插入初始数据:
INSERT INTO t_inventory(product_code, stock) VALUES ('P1001', 100);
第四步:编写业务代码(库存服务)
在 inventory-service 模块中编写一个接口来扣除库存:
@Service
public class InventoryService {
@Autowired
private InventoryMapper inventoryMapper;
@Transactional
public void deductStock(String productCode, int quantity) {
Inventory inventory = inventoryMapper.selectByProductCode(productCode);
if (inventory.getStock() < quantity) {
throw new RuntimeException("库存不足");
}
inventory.setStock(inventory.getStock() - quantity);
inventoryMapper.updateById(inventory);
}
}
注意:这个方法是被事务注解修饰的方法,会被 Seata 自动增强,参与全局事务。
第五步:调用链开始 —— 订单服务发起请求
在订单服务中发起调用:
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private AccountClient accountClient; // Feign 远程调用
@Autowired
private InventoryService inventoryService;
@GetMapping("/place")
@GlobalTransactional // Seata 注解开启全局事务
public String placeOrder() {
try {
// 1. 扣库存
inventoryService.deductStock("P1001", 1);
// 2. 扣余额
accountClient.deductBalance(50L); // 假设商品价格是50元
} catch (Exception e) {
return "下单失败:" + e.getMessage();
}
return "下单成功!";
}
}
注意:这里的 @GlobalTransactional 是关键注解,它标记这是一个需要跨服务协调的事务。
第六步:测试异常情况
我们故意在账户服务制造一个错误:
@GetMapping("/deduct-balance")
public void deductBalance(@RequestParam Long amount) {
if (amount > 50) {
throw new RuntimeException("金额过大");
}
// 正常逻辑:减余额
}
现在访问 /order/place?amount=100,你会看到所有前面的操作都被自动回滚了!
这就是 Seata 在背后做的工作:记录每一步变更,发生异常则撤销所有操作。
常见问题答疑(FAQ)

🔍 问:分布式事务会不会影响性能?
答:会的。由于增加了日志记录和网络通信,分布式事务相比本地事务确实慢一些。但 Seata 的 AT 模式已经尽量减少影响,适合大部分中小规模场景。
🔍 问:我的事务没有生效怎么办?
常见原因:
- 没有加上
@GlobalTransactional注解 - Seata 服务未启动
- 数据库没有配置 undo_log 日志表(这是 Seata 自动管理事务的关键表)
解决方法:
- 查看日志:Seata Client 会打印事务 ID 和状态
- 使用 MySQL 工具查询
undo_log表是否有记录
🔍 问:可以不用 Seata 吗?还有别的替代吗?
当然可以,比如:
- 使用 RocketMQ + 补偿机制(最大努力通知)
- 使用 TCC 模式自己写补偿逻辑(较复杂)
- 使用数据库 XA 协议(强一致性但性能差)
如果你是初学者,建议优先掌握 Seata,因为它的社区活跃、文档完善、案例丰富。
学习建议:下一步怎么学?
恭喜你完成了第一个分布式事务实战!接下来你可以继续深入学习以下几个方向:
✅ 进阶方向一:理解 TCC 模式与补偿机制
- 学习 TCC 的 Try、Confirm、Cancel 流程
- 自己动手写一个 TCC 示例
- 掌握幂等性和重试机制
✅ 进阶方向二:深入 Seata 的底层原理
- 理解事务协调器的工作机制
- 学习 AT 模式的 SQL 解析与反向 SQL 生成
- 探索 Seata 如何实现事务隔离级别
✅ 进阶方向三:结合消息队列优化最终一致性
- 使用 RabbitMQ 或 Kafka 发送事件通知
- 实现基于消息的最大努力通知模式
- 构建更加健壮的服务间通信机制
总结
本文带你了解了什么是分布式事务,为什么它重要,并且通过 Spring Boot + Seata 实现了一个完整的实战案例。
重点知识回顾:
| 概念 | 内容 |
|---|---|
| 什么是事务 | 多个操作要么全成功,要么全失败 |
| 什么是 Seata | 国内流行的开源分布式事务中间件,支持 AT 模式 |
| 核心组件 | TC(事务协调器)、TM(事务管理器)、RM(资源管理者) |
| 实际应用 | 订单系统中库存、账户、订单三个服务的数据一致性保障 |
| 注意事项 | 日志表 undo_log 必须存在、Seata Server 必须启动、事务必须被正确注解 |
希望你能在这个基础上继续探索分布式系统的更多内容,比如服务注册发现、链路追踪、熔断降级等。
如有疑问,欢迎留言交流,我们一起成长进步 💪

评论 0