MySQL两阶段提交 - saviochen/mysql_docs GitHub Wiki

1 分布式事务两阶段提交

在分布式事务处理中,全局事务会访问和更新多个局部数据库中的数据,如果要保证全局事务中的原子性,执行全局事务T的所有节点必须执行的最终结果取得一致。X/Open组织针对分布式事务处理而提出了XA规范,使用两阶段提交协议(two-phase commit protocal, 2PC)来保证一个全局事务T要么在有节点都提交,要么在所有节点都终止。

1.1 提交协议

考虑一个全局事务T的事务协调器(transaction coordinator)是 C,当执行T的所有事务管理器(transaction manager)都通知C已经完成了执行,C开始启动两阶段提交协议,分为prepare和commit两阶段:

1.2 prepare阶段

C将一条prepare消息发送到执行T的所有节点上。当每个节点的事务管理器都收到prepare消息时,确定是否远离提交事务T中自己的部分:如果可以提交,将T相关的所有日志记录强制刷盘,并记录事务T的状态为prepared,然后将事务管理器返回ready作为应答;如果无法提交,就发送abort消息。

1.3 commit阶段

当C接收到所有节点堆prepare消息的回应后进入commit阶段,C可以决定是将事务T进行提交还是中止,如果所有参与的阶段都返回了ready应答,则事务T可以提交,否则事务T需要中止。之后协调器C向所有节点发送commit或者abort消息,各节点收到这个消息后,将事务最终的状态更改为commit或者abort,并写入日志。

1.4 优缺点

XA使用两阶段提交协议的主要优点是原理简洁清晰、实现方便。主要缺点是各个节点需要阻塞等待事务协调器来决定提交或中止。如果事务协调器出现故障,那全局事务就无法获得最终的状态,各个节点可能需要持有锁并等待事务协调器的恢复,这种情况称为阻塞问题,因为事务 T 需要等待协调器恢复而被阻塞。

2 MySQL 内部 XA

MySQL 存在两个日志系统:server 层的 binlog 日志和 storage 层的事务日志(例如,InnoDB 的 redolog 日志),并且支持多个存储引擎。这样产生的问题是,如何保证事务在多个日志中的原子性,即,要么都提交,要么都中止。在单个 MySQL 实例中,使用了内部 XA 的方式来解决上述问题,其中,server 层作为事务协调器,而多个存储引擎作为事务参与者。

2.1 内部XA事务两阶段提交过程

MySQL采用了如下的过程实现内部XA的两阶段提交:

  1. prepare阶段:innodb将回滚段设置为prepare状态,将redo log写入并发刷盘。当写完redo log并将它标记为prepare状态时,并且会在redo log中记录一个XID,它全局唯一的标识着这个事务。
  2. commit阶段:binlog写入文件,binlog刷盘,innodb commit。 两阶段提交保证了事务在多个引擎和binlog之间的原子性,以binlog写入成功为事务提交的标志,而binlog的commit标志不是事务成功与否的标志。在崩溃恢复中,是以binlog中的xid和redo的xid进行比较,xid在binlog中存在则提交,不存在则回滚。具体如下:
  • 在prepare阶段崩溃,即已经写入redo log,在写binlog之前崩溃,则会回滚。
  • 在commit阶段,当没有成功写入binlog时崩溃,则会回滚。
  • 如果已经写入binlog,在写入innodb commit标志时崩溃,则重新写入commit标志,完成提交。

注意:上述过程中redo log其实不会记录XID,这样写只是为了简化内部XA的流程,隐藏MySQL内部XA中与undo log的相关的实现细节:实际上XID是记录在undo page上的undo log header中,事务的prepare、commit状态是记录在undo slot的第一个undo page上,而undo page也是受redo log保护的。undo log的详细介绍见《InnoDB Undo Log详解》。