2、分布式事务基础:本地事务与分布式事务、ACID特性在分布式环境下的挑战、XA协议与2PC模型

好,咱们今天聊聊分布式事务的底子。说实话,很多同学一上来就追各种分布式事务框架,结果遇到问题就懵了。我个人习惯是,先把地基打牢——本地事务、ACID、XA协议、2PC,这些搞明白了,后面看Seata、TCC那些方案,你一眼就能看穿它们的本质。

2.1 本地事务:你每天都在用的老朋友

先说说本地事务。说白了,就是在一个数据库连接里完成的事务。比如你在MySQL里执行一个转账操作:

BEGIN;
UPDATE account SET balance = balance - 100 WHERE user_id = 1;
UPDATE account SET balance = balance + 100 WHERE user_id = 2;
COMMIT;

这就是最经典的本地事务。它依赖数据库自身的日志和锁机制来保证数据一致性。我在项目中遇到过不少新人,觉得本地事务太简单,结果在并发场景下踩了坑——比如忘了设置合适的隔离级别,导致脏读、幻读。

我的小建议: 本地事务虽然基础,但它是理解分布式事务的起点。你想想看,如果连单机事务都搞不定,分布式环境下的问题只会更复杂。

2.2 分布式事务:当数据散落在多个节点

那分布式事务呢?就是一次操作涉及多个数据库、多个服务。比如下单场景:订单库扣库存、支付库扣钱、积分库加积分。这三个操作必须要么全成功,要么全失败。

为什么会这么麻烦?因为每个数据库都有自己的事务管理器,它们之间没法直接通信。你没法让MySQL和Redis说:「嘿,咱俩一起提交吧。」

我记得有一次,一个同事在微服务里直接用了本地事务,结果订单创建成功了,库存却扣失败了。用户付了钱,但商品没货——这就是典型的分布式事务问题。

2.3 ACID特性在分布式环境下的挑战

ACID这四个特性,在单机数据库里很完美。但到了分布式环境,每个特性都面临挑战。我一个个说。

ACID特性 单机环境 分布式环境挑战
原子性(Atomicity) 数据库通过undo log保证 多个节点间无法共享undo log,需要协调者
一致性(Consistency) 约束由数据库自身检查 跨节点数据约束需要应用层保证
隔离性(Isolation) 锁机制和MVCC 分布式锁、全局快照难以实现
持久性(Durability) 写入磁盘即保证 节点宕机后数据可能丢失,需要副本同步

你看这个表格就清楚了。原子性在分布式环境里,最大的问题就是「部分失败」——有的节点提交了,有的节点回滚了。我曾经在一个金融项目里遇到过这种情况:A服务扣款成功,B服务加积分时网络超时,结果钱扣了积分没加。用户投诉,我们查了半天日志才定位到问题。

避坑指南: 分布式环境下,千万不要假设网络是可靠的。我曾经因为忽略了网络超时导致数据不一致,后来花了整整两天才修复。记住:网络分区、节点宕机、超时重试,这些都是常态。

2.4 XA协议:分布式事务的「老大哥」

XA协议是X/Open组织提出的分布式事务规范。它定义了一个全局事务管理器(Transaction Manager,简称TM)和多个资源管理器(Resource Manager,简称RM)之间的交互接口。

说白了,就是引入一个「协调者」来管理所有参与事务的数据库。每个数据库都实现XA接口,告诉协调者:「我准备好了」或者「我失败了」。

XA协议的核心思想是两阶段提交(2PC)。嗯,这里要注意,2PC不是XA的全部,但它是XA最经典的实现方式。

2.5 2PC模型:两阶段提交详解

2PC分为两个阶段:准备阶段(Prepare Phase)和提交阶段(Commit Phase)。我画个流程图你感受一下:

协调者(TM)                   参与者1(RM1)              参与者2(RM2)
    |                              |                          |
    |-------- 准备请求 ------------>|                          |
    |                              |--- 记录undo/redo日志 ---->|
    |                              |                          |
    |<------- 准备成功 ------------|                          |
    |                              |                          |
    |-------- 准备请求 ------------>|                          |
    |                              |                          |
    |<------- 准备成功 --------------------------------------|
    |                              |                          |
    |-------- 提交请求 ------------>|                          |
    |                              |--- 提交事务 ------------->|
    |                              |                          |
    |<------- 提交成功 ------------|                          |
    |                              |                          |
    |-------- 提交请求 ------------>|                          |
    |                              |                          |
    |<------- 提交成功 --------------------------------------|

第一阶段:准备阶段

  • 协调者向所有参与者发送准备请求
  • 参与者执行事务,但不提交
  • 参与者将undo和redo日志写入磁盘
  • 参与者回复「可以提交」或「不能提交」

第二阶段:提交阶段

  • 如果所有参与者都回复「可以提交」,协调者发送提交请求
  • 如果有任何一个参与者回复「不能提交」,协调者发送回滚请求
  • 参与者收到提交或回滚请求后,执行相应操作

你想想看,这个模型看起来挺完美的对吧?但实际用起来,坑不少。

2PC的三大痛点:
  1. 同步阻塞: 准备阶段,参与者会锁住资源。我在项目中遇到过,一个慢查询导致整个事务卡住,其他请求全部排队等待。
  2. 单点故障: 协调者挂了,所有参与者都处于「悬而未决」的状态。我曾经在凌晨三点被叫起来处理这种问题——协调者宕机,数据库里一堆未提交的事务。
  3. 数据不一致: 第二阶段,如果协调者发送了提交请求,但部分参与者没收到,就会出现部分提交、部分未提交的情况。

我记得有一次,我们用XA协议做跨库转账。测试环境一切正常,上线后遇到网络抖动,结果A库提交了,B库没收到提交指令。最后只能手动查日志,一条一条补数据。嗯,从那以后,我对2PC就多了一份敬畏。

2.6 什么时候该用XA/2PC?

说实话,现在纯XA协议用得少了。因为它的性能开销大,而且对数据库有要求(必须支持XA)。但在某些强一致性场景下,它还是不可替代的。

我个人建议:

  • 如果业务对一致性要求极高(比如金融核心账务),且数据量不大,可以考虑XA
  • 如果追求高并发、高可用,建议用TCC、Saga等柔性事务方案
  • 如果只是简单的跨库操作,可以考虑用消息队列做最终一致性
我的经验: 不要为了用分布式事务而用分布式事务。很多场景下,通过合理的业务设计(比如异步对账、补偿机制)就能解决问题。分布式事务是最后的手段,不是首选方案。

好了,这一章的内容就到这里。下一章我们会深入聊聊TCC模式,那是一种更轻量、更灵活的分布式事务方案。到时候我会结合一个实际的订单系统案例,带你手写TCC代码。