分布式事务解决方案:最佳实践(新手友好版)
开篇:什么是分布式事务?为什么要用它?

在我们学习写程序的时候,最开始都是写一个简单的“小卖部系统”,比如下单、减库存这些操作都在同一个数据库里。这个时候,只要使用一个BEGIN TRANSACTION就能保证数据不出错。
但现实中的项目往往没那么简单。很多大型系统是由多个服务组成的,比如下单是一个服务,支付是另一个服务,库存管理又是另一个服务。它们可能运行在不同的服务器上,连接着不同的数据库。
这时候问题就来了:
如果用户下单成功了,但支付失败了,那库存是不是要回退呢?
这就引出了我们要讲的主题:分布式事务 —— 一种让我们能在多个服务、多个数据库之间保持数据一致性的机制。
环境准备:搭建你的第一个微服务环境


本教程将使用Java + Spring Boot作为开发语言,并配合Seata来处理分布式事务。
第一步:安装开发工具
你需要安装以下工具:
- JDK 1.8 或以上
- Maven 3.x
- IntelliJ IDEA(推荐)
- MySQL 5.7+ (也可以用Docker快速启动)
✅ 新手建议:如果你对命令行不熟悉,可以下载MySQL Workbench 和 IntelliJ IDEA Community Edition 来辅助开发。
第二步:安装 Seata Server(这是我们的分布式事务协调器)
Seata 是阿里巴巴开源的一个分布式事务中间件,非常适合作为入门工具。
安装步骤如下:
- 前往官网下载:https://seata.io 下载最新版本(例如
seata-server-1.6.1.zip)。 - 解压文件到本地目录,进入
conf目录,修改配置文件:- 修改
registry.conf文件,设置注册中心为file(即本地文件方式),方便初学者调试。
- 修改
registry {
type = "file"
}
- 启动 Seata Server:
# Windows下双击 startup.bat 即可
# Linux/Mac 下执行:
sh seata-server.sh -p 8091 -m file
- 成功后你会看到日志输出中有
Server started字样,说明Seata已经准备好工作了。
核心概念:轻松理解分布式事务的几个关键词

1. 什么是本地事务?
在单个数据库中,我们熟悉的事务就是本地事务。它满足 ACID 特性(原子性、一致性、隔离性、持久性)。例如:
@Transactional
public void transferMoney() {
jdbcTemplate.update("UPDATE account SET balance = balance - 100 WHERE name = '张三'");
jdbcTemplate.update("UPDATE account SET balance = balance + 100 WHERE name = '李四'");
}

这段代码要么全部执行成功,要么都不执行,不会出现只转账了一个人的情况。
2. 什么是全局事务?
当我们在多个服务之间进行操作时(如下单、付款、减库存),我们就需要一个“统一指挥”的角色,告诉各个服务要不要提交或回滚,这个“指挥官”就是 Seata 所提供的 全局事务协调者。
你可以想象它是这样工作的:
- 下单服务先登记自己参与了事务
- 支付服务也登记自己参与了事务
- 如果一切顺利,协调者说:“都提交”
- 如果某一步出错,协调者说:“大家都回滚”
3. 几种常见的分布式事务模式(简单对比)
| 名称 | 是否自动回滚 | 使用场景 | 是否适合新手 |
|---|---|---|---|
| XA | 是 | 数据库支持严格一致性 | ❌复杂 |
| TCC | 是 | 可控的服务逻辑 | ✅较适合 |
| Saga | 是 | 长流程业务 | ⚠️有一定难度 |
| AT (Auto Transaction) | 是 | 对数据库透明操作 | ✅推荐 |
今天我们主要讲解 AT 模式,因为它对开发者最友好,只需加注解即可。
实战项目:写一个下单+扣库存的例子
我们将创建两个服务:
order-service(下单服务)inventory-service(库存服务)
这两个服务通过 Restful 接口调用彼此,并且使用 Seata 保证一致性。
第一步:创建数据库和表
在MySQL中分别创建两个库:
CREATE DATABASE order_db;
CREATE DATABASE inventory_db;
USE order_db;
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
product_id BIGINT NOT NULL,
user_id BIGINT NOT NULL
);
USE inventory_db;
CREATE TABLE inventory (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
product_id BIGINT NOT NULL UNIQUE,
stock INT NOT NULL DEFAULT 0
);
插入一条测试数据:
INSERT INTO inventory (product_id, stock) VALUES (1, 100);
第二步:编写项目结构(Spring Boot)
创建两个Maven模块:
project-root/
├── order-service/
│ └── pom.xml
│ └── src/main/java/...
└── inventory-service/
└── pom.xml
└── src/main/java/...
公共依赖(pom.xml 中都需要加入的):
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.6.1</version>
</dependency>
第三步:配置Seata客户端
在两个服务的 application.yml 中添加以下配置:
seata:
enabled: true
application-id: order-service # 不同服务改为 inventory-service
tx-service-group: my_tx_group
Seata会根据 tx-service-group 自动去连接你刚才启动的 Server。
第四步:编写 OrderService
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/create")
public String createOrder(@RequestParam Long productId) {
orderService.createOrder(productId);
return "订单创建完成!";
}
}
实现类:
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
@GlobalTransactional // 关键:开启全局事务
@Override
public void createOrder(Long productId) {
// 1. 插入订单
orderMapper.insertOrder(productId, 1L); // 假设用户id是1
// 2. 调用库存服务
String url = "http://localhost:8081/inventory/deduct?productId=" + productId;
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
if (!"SUCCESS".equals(response.getBody())) {
throw new RuntimeException("库存不足或系统异常");
}
}
}
📌
@GlobalTransactional就是我们引入 Seata 的关键注解!
第五步:编写 InventoryService
接口:
@GetMapping("/deduct")
public String deductStock(@RequestParam Long productId) {
boolean success = inventoryService.deduct(productId);
return success ? "SUCCESS" : "FAIL";
}
实现类:
@Service
public class InventoryServiceImpl implements InventoryService {
@Autowired
private InventoryMapper inventoryMapper;
@Transactional
@Override
public boolean deduct(Long productId) {
Integer affected = inventoryMapper.deductStock(productId);
return affected > 0;
}
}
SQL(mapper.xml):
<update id="deductStock">
UPDATE inventory SET stock = stock - 1 WHERE product_id = #{productId} AND stock > 0
</update>
第六步:运行项目测试
- 启动两个服务,端口号分别是8080(order)、8081(inventory)。
- 启动Seata Server。
- 浏览器访问:
http://localhost:8080/order/create?productId=1 - 成功则看到“订单创建完成!”
- 故意改错代码模拟异常,比如让库存服务抛出异常,检查是否回滚。
常见问题解答
Q1:Seata无法连接,怎么办?
确保你本地有启动Seata Server,并检查 application.yml 中的配置是否正确。
Q2:@GlobalTransactional 注解没有生效?
确保你已添加 Seata Starter 依赖,并且启动类加上了 @EnableTransactionManagement。
Q3:事务跨服务为什么还能回滚?
因为 Seata 的 AT 模式会在你每个数据库操作前后记录“快照”,一旦失败,Seata 会帮你把这些快照“还原”。
Q4:TCC 和 AT 有什么区别?
- TCC 需要你自己定义 try-confirm-cancel 三个阶段,控制更精细,但也更复杂。
- AT 则由框架自动记录操作前后的状态,适合大多数 CRUD 场景,更适合刚入门的同学。
学习建议:下一步该学什么?
恭喜你完成了第一个分布式事务项目的实战!
接下来你可以尝试以下几个方向:
方向一:深入理解 Seata 的底层原理
- 了解 Seata 的 TC、TM、RM 架构
- 理解 AT 模式的 undo log 工作原理
方向二:尝试其他事务模式(如 TCC)
- 学习如何编写 Try 方法、Confirm 方法、Cancel 方法
- 更好地控制复杂的业务流程
方向三:集成 Nacos 或 Eureka 作为服务发现
这会更加贴近企业级架构,让你更好地理解服务间通信与协调。
总结
这篇文章从零开始带你认识了什么是分布式事务,学会了如何搭建 Seata 环境,并动手完成了第一个“下单+扣库存”项目。整个过程强调“看得懂、做得出来、能跑通”。
分布式事务并不是遥不可及的技术难点,只要掌握了核心思想和基础工具(如 Seata),就可以一步步构建稳定可靠的企业级应用系统。
继续加油,未来的架构师!🚀
如果你觉得这篇教程对你有帮助,欢迎点赞/收藏/分享让更多人看到!

评论 0