[ 分布式事务,解决方案 01 ]
先确认你的两个问题
数据库事务 = 单库。 更准确说是单个数据库连接。MySQL 的 BEGIN/COMMIT 只对这一个连接上的操作生效,它根本不知道别的库、别的服务在干什么。
@Transactional 就是它。 Spring 只是帮你在方法进入时 BEGIN、正常返回时 COMMIT、抛异常时 ROLLBACK。本质上还是那一个数据库连接。项目里配了多数据源,一个方法也只能管住其中一个。
一个例子讲透:下单扣库存
场景 A:没拆分,一个库
@Transactional
public void createOrder(Order order) {
orderMapper.insert(order); // 订单表
stockMapper.decrease(order.getSkuId(), 1); // 库存表
}
同一个库、同一个连接,MySQL 保证这两条 SQL 要么都成、要么都回滚。这里完全不需要分布式事务。
场景 B:拆成两个服务、两个库
订单服务连订单库,库存服务连库存库。你天真地改成:
@Transactional
public void createOrder(Order order) {
orderMapper.insert(order); // 订单库
stockClient.decrease(skuId, 1); // ❌ HTTP 调库存服务
}
这段代码有三个致命问题:
- 回滚不了对方。 库存扣成功了,但接下来
insert抛异常,本地事务回滚了订单——库存却真的少了 1 个。远程操作不会跟着你回滚。 - 超时你不知道结果。
decrease超时了,库存到底扣没扣?你无法判断。重试可能扣两次,不重试可能没扣。 - 长事务。 HTTP 调用可能几秒,这期间数据库连接和行锁一直被占着,并发一高连接池就爆。
核心结论:@Transactional 里绝对不能写远程调用。
场景 C:正确做法——本地消息表
思路是放弃"同时成功",改成"迟早都会成功"。分两段:
第一段:订单服务
在订单库里额外建一张 message 表(消息表),然后:
@Transactional // 这两条都在订单库,一个本地事务能盖住
public void createOrder(Order order) {
orderMapper.insert(order);
messageMapper.insert(new Message(
msgId, // 全局唯一ID
"扣库存: skuId=1, qty=1",
"待发送"
));
}
// 事务提交后,再由异步线程/定时任务把消息发到 MQ
这一步是整个方案的精髓: 消息表和订单表在同一个库,所以能用一个本地事务捆死。只要订单写成功了,"要扣库存"这件事就一定被记下来了,不可能丢。
第二段:库存服务
消费消息时:
@Transactional // 这两条都在库存库,一个本地事务能盖住
public void onMessage(String msgId, Long skuId) {
dedupMapper.insert(msgId); // 去重表,msgId 建唯一索引
stockMapper.decrease(skuId, 1);
}
如果这条消息被重复投递,insert(msgId) 会因为唯一索引冲突抛异常,整个事务回滚,库存不会被扣第二次。这就是幂等。
因为幂等了,所以 MQ 可以放心地无限重试,直到成功为止。整个链路里,每一段都是普通的本地事务,中间靠消息串起来。没有任何一个"跨库的大事务"存在。

中间态:用户看到什么
订单创建后到库存扣成功之间,可能有几百毫秒甚至几秒。这段时间订单状态是"处理中"。这就叫最终一致性——不是任何时刻都一致,而是保证过一会儿一定会一致。
业务上要接受这一点:显示"订单处理中",而不是骗用户说已完成。
你不熟的名词,对照着看
| 名词 | 在上面例子里对应什么 |
|---|---|
| 本地事务 | @Transactional 圈住的那一段,单库内 |
| 幂等 | 库存服务插去重表,重复消息进不来 |
| 最终一致性 | 订单先"处理中",稍后才变"已完成" |
| 本地消息表 | 订单库里那张 message 表 |
| 事务消息 | RocketMQ 内置的功能,替你实现了消息表这套逻辑,少写代码 |
| 补偿 | 库存不足彻底扣不了 → 反向调用"取消订单"把订单关掉 |
| 2PC / XA | 有个协调者指挥所有库一起提交,强一致但慢,基本不用 |
| TCC | 把扣库存拆成 Try(冻结)/ Confirm(真扣)/ Cancel(解冻),性能好但要写三套代码 |
| Saga | 多个步骤依次执行,某步失败就把前面的步骤逐个反向撤销 |
一句话对比
- 本地事务:一个库说了算,能真正回滚。
- 分布式事务:谁也说了不算,只能靠「记下来 + 一直重试 + 幂等保护 + 实在不行就补偿」,换来最终一致。
分布式事务不是更强的事务,是更弱的事务。 你为了拆分服务,付出的代价就是失去了强一致性。

浙公网安备 33010602011771号