摘要
单机事务靠 Redo + Undo,分布式事务靠什么?答案藏在 XA 两阶段提交共识协议这两条截然不同的技术路径中。MySQL 同时站在两条路的起点:内部 XA 协调 Binlog 与 InnoDB,是每个提交事务的必经之路;外部 XA 允许应用作为协调者,在多个 MySQL 实例间实现分布式事务;而 Group Replication 则将 Paxos 共识协议引入数据库内核,使集群本身就具备强一致、自动选主、多主写入的能力。

本文从 XA 规范与 DTP 模型入手,系统拆解内部 XA 的源码实现(tc_logprepare_commit_mutex、崩溃恢复 XID 比对),完整还原外部 XA 的事务分支定义与两阶段提交流程,并深入剖析其长期存在的崩溃安全缺陷及 8.0.33 的修复方案。在此基础上,将视角从“协调多个 MySQL”转向“MySQL 自身成为分布式系统”——详解 Group Replication 基于 Paxos 变种的事务提交流程、冲突检测原理及单主/多主模式的设计取舍。生产实践部分提供 XA 事务监控、MGR 网络分区调优及分布式事务选型决策树。最后基于 2026 年视角,讨论共识协议下沉至存储层对传统 XA 架构的替代趋势。


一、核心概念与底层图景

1.1 定义

分布式事务指事务的参与者、资源服务器、事务管理器位于不同节点,需保证跨节点的 ACID。

MySQL 涉及两种完全不同的“分布式”场景:

类型协调者参与者典型场景一致性保证
内部 XAMySQL Server(tc_logBinlog + InnoDB(及其他事务引擎)单机事务,协调日志与数据引擎崩溃一致
外部 XA应用/中间件多个 MySQL 实例跨库事务,如分库分表强一致(需应用协调)
Group ReplicationPaxos 协议(XCom)集群内多个 MySQL 节点高可用、强一致集群强一致(多数派)

设计哲学

  • 内部 XA:解决架构分化——Server 层写 Binlog,引擎层写 Redo,两个日志必须原子提交。
  • 外部 XA:解决资源隔离——多个独立 MySQL 实例无共享状态,通过协议协调。
  • Group Replication:解决状态复制——将单机变成分布式状态机,共识协议取代协调者。

1.2 架构全景

graph TB
    classDef xa fill:#fff3e0,stroke:#e65100,stroke-width:2px
    classDef gr fill:#e1f5fe,stroke:#01579b
    classDef storage fill:#d1c4e9,stroke:#4a148c
    classDef app fill:#c8e6c9,stroke:#1b5e20

    subgraph 内部XA [单机内部XA]
        APP[客户端] -->|COMMIT| SERVER[MySQL Server]
        SERVER -->|tc_log->prepare| INNODB1[InnoDB Prepare]
        INNODB1 -->|写Prepare Redo| LOG[Redo Log]
        SERVER -->|tc_log->commit| BINLOG[Binlog Group Commit]
        BINLOG -->|XID写入| BINLOG_FILE[binlog文件]
        SERVER -->|tc_log->commit| INNODB2[InnoDB Commit]
        INNODB2 -->|写Commit Redo| LOG
    end

    subgraph 外部XA [跨实例XA]
        TM[应用/中间件] -->|XA START| M1[MySQL实例1]
        TM -->|XA START| M2[MySQL实例2]
        M1 -->|DML| DATA1
        M2 -->|DML| DATA2
        TM -->|XA PREPARE| M1
        TM -->|XA PREPARE| M2
        TM -->|XA COMMIT| M1
        TM -->|XA COMMIT| M2
    end

    subgraph 组复制 [MGR集群]
        direction TB
        P[主节点] -->|事务| XCOM[XCom Paxos]
        XCOM -->|多数派确认| B1[备节点1]
        XCOM -->|多数派确认| B2[备节点2]
        
        subgraph 冲突检测 [认证模块]
            CERT[Certification] --> WS[WriteSet哈希]
            WS --> COMPARE{冲突?}
            COMPARE -->|无冲突| COMMIT_GR[提交]
            COMPARE -->|冲突| ROLLBACK_GR[回滚]
        end
        
        P --> CERT
        B1 --> CERT
        B2 --> CERT
    end

    class APP,SERVER,INNODB1,INNODB2,BINLOG,LOG xa
    class TM,M1,M2,DATA1,DATA2 xa
    class P,B1,B2,XCOM,CERT,WS,COMMIT_GR,ROLLBACK_GR gr

二、内部 XA:Binlog 与 InnoDB 的千年之约

2.1 为什么单机还需要分布式事务?

MySQL 的插件式架构导致了一个根本矛盾:

  • Server 层负责 Binlog,它不知道存储引擎如何提交。
  • InnoDB 层负责 Redo,它不知道 Binlog 是否落盘。

若 Binlog 写成功但 InnoDB 提交失败 → 主从不一致(从库多执行事务)。
若 InnoDB 提交成功但 Binlog 写失败 → 主库数据完整,但从库无法同步。

内部 XA 的使命:将 Binlog 和 InnoDB 视为两个独立的“资源管理器”,由 Server 层作为事务协调器(TM),通过两阶段提交保证二者的原子性。

2.2 协调者三选一:tc_log 的抉择

MySQL 启动时,根据配置选择全局事务协调器对象 tc_log

/* sql/tc_log.cc - TC_LOG::init */
int TC_LOG::init(THD *thd) {
    if (opt_bin_log) {
        /* 开启 Binlog 且存在事务引擎 → mysql_bin_log 协调 */
        tc_log = &mysql_bin_log;
    } else if (total_ha_2pc > 1) {
        /* 关闭 Binlog,但存在 ≥2 个事务引擎 → tc_log_mmap(内存映射)*/
        tc_log = &tc_log_mmap;
    } else {
        /* 无事务引擎或仅单引擎 → 哑协调器,直接透传 */
        tc_log = &tc_log_dummy;
    }
}

三种协调器

协调器存储介质崩溃恢复依据场景
mysql_bin_logBinlog 文件Binlog 中的 XID生产默认,推荐
tc_log_mmaptc.log 内存映射文件文件中的 XID 数组无 Binlog 多引擎事务(极少)
tc_log_dummy无事务引擎(如 MyISAM)

2.3 两阶段提交源码全流程

/* sql/handler.cc - ha_commit_trans */
int ha_commit_trans(THD *thd, bool all) {
    /* 1. 确定事务范围:SESSION 或 STMT */
    Transaction_ctx::enum_trx_scope scope = all ? SESSION : STMT;
    
    /* 2. 获取已注册的引擎列表 */
    Ha_trx_info *ha_info = trn_ctx->ha_trx_info(scope);
    
    /* 3. 判断是否需要 2PC:参与者数量 > 1 */
    uint rw_ha_count = ha_check_and_coalesce_trx_read_only(thd, ha_info, all);
    bool need_2pc = (!trn_ctx->no_2pc(scope) && rw_ha_count > 1);
    
    if (need_2pc) {
        /* ===== 阶段一:PREPARE ===== */
        error = tc_log->prepare(thd, all);
        /* Binlog 的 prepare 为空操作 */
        /* InnoDB prepare: 修改事务状态 TRX_STATE_PREPARED,写 XID 到 Undo Header */
    }
    
    /* ===== 阶段二:COMMIT ===== */
    error = tc_log->commit(thd, all);
    /* Binlog commit: ordered_commit() 刷盘组提交 */
    /* InnoDB commit: 释放锁、清理 ReadView、写 commit redo */
}

InnoDB Prepare 核心trx0trx.cc):

void trx_prepare(trx_t *trx) {
    /* 1. 修改事务状态 */
    trx->state = TRX_STATE_PREPARED;
    
    /* 2. 将 XID 写入 Undo Log Header */
    trx_undo_set_state_at_prepare(trx);
    
    /* 3. 写 Prepare 记录的 Redo Log */
    lsn_t lsn = mlog_write_ulint(..., MLOG_UNDO_HDR_REUSE);
    
    /* 4. 刷 Redo Log(受 innodb_flush_log_at_trx_commit 控制)*/
    log_write_up_to(lsn, true);
}

Binlog Commit 核心sql/binlog.cc):

int MYSQL_BIN_LOG::commit(THD *thd, bool all) {
    /* ordered_commit 三阶段:FLUSH、SYNC、COMMIT */
    if (thd->tx_commit_pending) {
        ordered_commit(thd);
    }
}

2.4 崩溃恢复:以 XID 为唯一信标

恢复逻辑 :

-- 场景一:Redo Prepare,Binlog 未写 → 回滚
-- 场景二:Redo Prepare,Binlog 已写且包含 XID → 提交
-- 场景三:Redo Commit → 已提交

核心代码log0recv.cc + tc_log):

/* sql/binlog.cc - check_xids_in_binlog */
bool MYSQL_BIN_LOG::check_xids_in_binlog(XID *xid_list, uint *len) {
    /* 扫描最后一个 binlog 文件,提取所有 XID_EVENT 的 XID */
}
 
/* 恢复时,对每个处于 PREPARE 状态的事务: */
if (tc_log->is_committed(xid)) {
    trx_commit(trx);   /* binlog 存在 → 提交 */
} else {
    trx_rollback(trx); /* binlog 不存在 → 回滚 */
}

历史缺陷(5.7 及 8.0.33 之前):
XA PREPARE 阶段先写 binlog,再写 prepare redo
若写完 binlog 后崩溃,主库回滚,从库已收到 binlog → 主从不一致
8.0.33 修复:调整顺序,先写 prepare redo,再写 binlog。


三、外部 XA:应用协调的分布式事务

3.1 XA 规范与 DTP 模型

XA 规范定义了分布式事务处理(DTP)模型的三个角色 :

角色全称MySQL 中的角色
APApplication Program应用程序(发起事务)
TMTransaction Manager应用/中间件(协调者)
RMResource ManagerMySQL 实例(参与者)

MySQL 在外部 XA 中的定位资源管理器(RM),不是事务协调器。
TM 由应用层实现(如 Java JTA、Go DTm、自研协调器)。

3.2 XA 事务语法与状态机

-- 1. 分支事务开始
XA START 'xid1';
INSERT INTO t1 VALUES (1);
XA END 'xid1';
 
-- 2. 准备阶段(所有分支都执行成功后,TM 发起 PREPARE)
XA PREPARE 'xid1';
 
-- 3. 提交阶段(若所有分支 PREPARE 成功)
XA COMMIT 'xid1' [ONE PHASE];  -- ONE PHASE 跳过 prepare,单节点优化
 
-- 4. 回滚阶段(任一分支 PREPARE 失败)
XA ROLLBACK 'xid1';
 
-- 5. 崩溃恢复:查看处于 PREPARE 状态的事务
XA RECOVER;

XA 事务状态转换

ACTIVE → IDLE → PREPARED → COMMITTED
              ↘        → ROLLBACK

3.3 外部 XA 的核心缺陷(8.0.33 前)

缺陷一:崩溃不安全
MySQL 8.0.30 及之前版本,崩溃恢复时对处于 PREPARE 状态的外部 XA 事务统一不做处理
若 TM 在 PREPARE 全部成功后、COMMIT 前崩溃,MySQL 实例重启后这些事务仍处于 PREPARE 状态,锁未释放,数据未提交——事务悬挂

缺陷二:隔离级别限制
官方文档明确建议:外部 XA 事务应使用 SERIALIZABLE 隔离级别
原因:MySQL 的 MVCC 在分布式事务中无法保证全局快照一致性,RR 级别可能出现不可重复读。

缺陷三:性能开销巨大
每一条 XA 事务至少增加 2 次额外 fsync(PREPARE 刷 Redo + COMMIT 刷 Binlog),且 XA PREPARE 会持有 prepare_commit_mutex(5.6 前严重,5.7+ 已优化)。

3.4 外部 XA 的适用场景

✅ 仍可使用

  • 短周期、低并发、强一致要求极高的跨库转账(如银行核心)。
  • 已有成熟 JTA 中间件(Atomikos、Bitronix)的遗留系统。

❌ 不推荐

  • 高并发互联网业务(性能扛不住)。
  • 跨多个 MySQL 实例的分库分表场景(建议使用 Seata AT 模式或 DTm 二阶段消息)。

四、Group Replication:共识协议驱动的分布式数据库

4.1 从“协调外部”到“自协调集群”

外部 XA 需要应用层协调多个独立 MySQL 实例,实例之间无通信
Group Replication 则是实例之间通过 Paxos 协议直接通信,形成一个自治的分布式状态机 。

架构四层

层级组件职责
插件层Group Replication Plugin事务钩子(before_commit)、冲突检测、应用线程
GCS 层Group Communication System消息广播、成员管理、视图变更
Proxy 层GCS XCom Proxy消息序列化、队列转发
Paxos 层XCom共识协议、多数派日志复制

4.2 Paxos 变种与单主/多主

XCom 的实现特点

  • Mencius 变种:通过 (msgno, node_no) 唯一标识槽位,多主模式下轮询分配发送权。
  • Noop 机制:若节点在轮次中无数据发送,广播 noop_op 跳过该槽位。
  • 缺陷:多主模式下,慢节点无法发送数据也无法发送 Noop,会阻塞后续槽位,导致集群不可用 。

单主模式

  • 仅激活一个 Paxos 组,备节点不发送事务数据。
  • 优点:容忍少数派故障,网络抖动影响小。
  • 缺点:备节点发送管理信息仍需向主节点申请位置,延迟较高但频率低 。

4.3 事务提交流程(单主模式)

sequenceDiagram
    participant C as 客户端
    participant P as 主节点
    participant B as 备节点1
    participant B2 as 备节点2
    participant X as XCom

    C->>P: COMMIT
    P->>P: before_commit 钩子
    P->>P: 生成 Transaction_msg (WriteSet + Binlog)
    P->>X: 广播 Paxos accept_op
    X-->>B: 消息复制
    X-->>B2: 消息复制
    B-->>X: ack_accept_op
    B2-->>X: ack_accept_op
    Note over X: 多数派确认(2/3)
    X-->>P: 学习阶段完成
    P->>P: 冲突检测(仅切主时)
    P->>P: 写入 Binlog,提交事务
    P-->>C: OK
    X-->>B: executor_task 通知
    X-->>B2: executor_task 通知
    B->>B: 写入 Relay Log,Applier 回放

关键钩子group_replication_trans_before_commit
位置:事务 prepare 之后、binlog 写入之前 。

4.4 冲突检测与认证机制

冲突检测原理

  1. WriteSet 提取:Server 层为事务修改的每一行主键生成哈希值(库名+表名+主键值)。
  2. 快照版本:发送节点的 gtid_executed 集合,标识该事务开始前已提交的事务。
  3. 认证数组:每个节点维护哈希表,Key = 行哈希,Value = 最近修改该行的事务 GTID。
  4. 冲突判定
    • 若某行哈希的最近 GTID 不在发送节点的快照中 → 冲突,回滚事务。
    • 否则 → 通过,更新认证数组。

多主模式的写写冲突

  • 后提交的事务在备节点认证失败,回滚
  • 客户端收到 ER_LOCK_DEADLOCK,需应用层重试 。

4.5 流控与成员管理

流控(Flow Control)

  • 目的:防止慢节点被驱逐,导致集群抖动。
  • 原理:每 60 秒统计各节点的认证队列长度、应用队列长度,计算配额(quota)。
  • 参数:group_replication_flow_control_mode=QUOTAgroup_replication_flow_control_member_quota_percent=30

成员驱逐与自动重连

  • group_replication_member_expel_timeout(默认 5 秒):节点未响应心跳,标记为 UNREACHABLE。
  • 超时后,多数派节点将其驱逐出集群 。
  • group_replication_autorejoin_tries:被驱逐节点自动尝试重加入。

五、生产落地与 SRE 实战

5.1 场景化案例:外部 XA 悬挂事务恢复

现象

  • 应用报错 ERROR 1399 (XAE03): XA transaction branch has failed
  • XA RECOVER 显示多条处于 PREPARE 状态的事务,data 字段显示业务 XID。

恢复步骤

-- 1. 确认事务是否可以提交(与应用协调者确认)
XA RECOVER;
-- 2. 手工提交或回滚
XA COMMIT 'xid1';
-- 或
XA ROLLBACK 'xid1';
 
-- 3. 若 XID 已无法追溯,且事务已阻塞业务锁
-- 8.0.33+ 支持强制回滚
XA ROLLBACK FORCE 'xid1';

预防措施

  • 升级至 MySQL 8.0.33+(修复崩溃恢复缺陷)。
  • 应用层协调者需实现事务日志,重启后根据日志恢复 XA 决策。

5.2 场景化案例:MGR 网络分区导致集群只读

环境

  • 3 节点 MGR 集群,单主模式,跨可用区部署。
  • 网络抖动导致 1 节点被驱逐,剩余 2 节点仍满足多数派,集群可写。
  • 被驱逐节点恢复网络后,自动重加入,状态 RECOVERING 卡住。

排查

SELECT * FROM performance_schema.replication_group_members;
-- member_state: RECOVERING
 
SELECT * FROM performance_schema.replication_group_member_stats\G
-- 查看 recovery 进度

优化

[mysqld]
-- 1. 延长驱逐超时,避免网络毛刺误判
group_replication_member_expel_timeout = 10
 
-- 2. 启用克隆插件加速恢复
group_replication_recovery_use_clone = ON
 
-- 3. 设置超时后自动重试
group_replication_autorejoin_tries = 3

5.3 参数调优矩阵

领域参数8.0 推荐值内核解释
内部 XAinnodb_support_xaON(默认)5.7 参数,8.0 移除,始终启用
binlog_group_commit_sync_delay1000(微秒)组提交延迟,提升内部 XA 效率
外部 XAxa_detach_on_prepareON(8.0.33+)解决 XA 悬挂问题
innodb_rollback_on_timeoutOFFXA 事务超时是否回滚
MGRgroup_replication_consistencyAFTER(单主)强一致读/写等级
group_replication_poll_spin_loops0XCom 协程自旋,默认自适应
group_replication_flow_control_modeQUOTA生产环境必开启
group_replication_flow_control_applier_threshold25000应用队列积压阈值
group_replication_transaction_size_limit150000000(≈150MB)大事务限制,防阻塞

5.4 监控与诊断

1. 内部 XA 状态

-- 5.7 查看 XA 事务
SHOW ENGINE INNODB STATUS\G
-- 搜索 "TRANSACTIONS" 段,查看 ACTIVE/PREPARED 事务
 
-- 8.0 performance_schema
SELECT * FROM performance_schema.transaction
WHERE STATE = 'PREPARED';

2. 外部 XA 监控

-- 查看所有 PREPARE 状态事务
XA RECOVER;

3. MGR 集群监控

-- 成员状态
SELECT member_host, member_port, member_state, member_role
FROM performance_schema.replication_group_members;
 
-- 事务积压与冲突
SELECT * FROM performance_schema.replication_group_member_stats\G
-- 关键字段:
--   COUNT_TRANSACTIONS_IN_QUEUE: 等待认证队列
--   COUNT_TRANSACTIONS_REMOTE_IN_APPLIER_QUEUE: 等待应用队列
--   COUNT_TRANSACTIONS_CHECKED: 已认证事务数
--   COUNT_CONFLICTS_DETECTED: 冲突事务数(多主核心指标)

4. 流控观测

SHOW STATUS LIKE 'group_replication_flow_control%';
-- group_replication_flow_control_held: 是否处于流控状态

5.5 故障排查决策树

mindmap
  root(分布式事务问题)
    内部XA (Binlog/InnoDB不一致)
      主从数据不一致
        检查是否早期版本(5.7)
        升级至 8.0.33+
      崩溃恢复慢
        innodb_log_file_size 过大
        调整检查点频率
    外部XA
      事务悬挂 (PREPARE 后无决策)
        MySQL 8.0.30 及以下
        升级 + XA RECOVER 手工清理
      XAER_RMFAIL
        网络超时
        增加 connect_timeout
    组复制
      节点 RECOVERING 卡住
        recovery 增量恢复慢
        启用克隆插件
      流控频繁
        慢节点性能不足
        增加资源或调低配额
      多主冲突严重
        业务拆分
        评估是否需要多主

六、技术演进与 2026 年视角

6.1 历史设计约束与改进

版本分布式事务变化动因
5.0引入外部 XA支持分布式事务
5.5内部 XA 完善,tc_log 协调解决 Binlog/InnoDB 一致
5.7.17MGR GA引入 Paxos 共识
8.0.14动态权限、部分 SUPER 拆分MGR 运维权限细化
8.0.21MGR 流控增强,member_expel_timeout网络分区容忍性
8.0.33修复 XA 崩溃恢复缺陷解决长期悬挂问题

6.2 2026 年仍存在的“遗留设计”

  1. 外部 XA 性能与隔离级别的根本矛盾
    XA 要求 SERIALIZABLE 才能完全保证一致性,但生产环境极少使用该级别。
    现状:官方未解决,应用层需接受 RR 下的极小概率不一致。

  2. MGR 多主模式的“Noop 阻塞”缺陷
    慢节点既不能发数据,也不能发 Noop → 后续槽位阻塞 → 集群不可用 。
    现状:官方推荐单主模式,多主仅适用于特定场景。

  3. MGR 大事务限制
    group_replication_transaction_size_limit 默认 150MB,超过即报错。
    原因:XCom 消息过大导致网络缓冲溢出、节点超时。
    现状:无解,需业务拆分大事务。

  4. 内部 XA 的 2PC 无法绕过
    即使只有一个事务引擎 + Binlog,仍要走完整两阶段提交。
    代价:每次提交至少两次 fsync(Redo prepare + Binlog commit)。
    现状:8.0 组提交优化后大幅改善,但无法消除。

6.3 未来趋势:共识协议下沉存储层

云原生数据库的答案

  • AWS Aurora / 阿里云 PolarDB:共享存储,计算节点无状态。跨节点事务无需 2PC,存储层多版本解决一致性问题。
  • TiDB / CockroachDB:原生分布式,Raft 共识协议,XA 被彻底抛弃

MySQL 社区版的未来

  • MGR + MySQL Router:官方主推的高可用方案,但对业务透明的一致读需 group_replication_consistency=BEFORE,性能代价较高。
  • 外部 XA 将边缘化:除非强一致跨库事务成为刚需且无替代方案,否则不推荐新项目使用

2026 年现实

内部 XA 仍是 MySQL 单机事务的基石,无法绕过。
外部 XA 已进入“维护模式”,仅修复严重 bug,无新特性开发。
MGR 单主模式成为高可用事实标准,多主模式作为“特例”存在。


七、结语:分布式事务,没有银弹

MySQL 同时站在分布式事务的三条岔路口:

  • 内部 XA——你每天都在用,却从未感知。它是 Binlog 与 InnoDB 之间的“婚姻契约”,每一次 COMMIT 都在执行轻量级的分布式协调。
  • 外部 XA——最正统的分布式事务,但也是最笨重的那一个。它为强一致而设计,却被性能与复杂性反噬。
  • Group Replication——不是事务协调,而是状态机复制。它让 MySQL 从“单机”进化为“集群”,但也暴露了 Paxos 工程实现的诸多妥协。

2026 年的选择

  • 单机事务:信任内部 XA,它足够成熟。
  • 跨库事务:优先考虑业务拆分,而非 XA。
  • 集群事务:用 MGR 单主模式,接受最终一致读付费强一致读

分布式系统的第一课就是:没有免费的强一致。


参考文献

  • sql/handler.cc, sql/binlog.cc, storage/innobase/trx/trx0trx.cc MySQL 8.0.33 源码
  • plugin/group_replication/src/ MGR 源码
  • MySQL Internals Manual – XA Transactions, Group Replication
  • Oracle Blogs: “MySQL 8.0.33: XA Transaction Crash Recovery Fix” (2023)
  • Oracle Blogs: “Group Replication: Single-Primary vs Multi-Primary” (2019)
  • 阿里云 RDS 组复制文档
  • 网易 MGR 源码分析
  • 百度 MGR 深度解析
  • 华为云 MySQL 8.0 事务提交原理
  • MySQL 8 中文参考(组复制架构)
  • MySQL Group Replication(MGR)事务执行过程
  • MySQL 集群事务处理
  • MySQL XA 事务使用及缺陷