MySQL 分为 server 层和存储引擎层,事务由存储引擎层实现。如果修改服务器 A 中数据库的数据的同时也需要修改服务器 B 中数据库的数据,这两个操作必须同时成功或者失败,也就是必须保证原子性
而这两个操作属于两个不同的数据库,存储引擎也不是同一个,无法保证两个操作的原子性。这个时候就需要一个全局事务,由若干个小事务组成,这个全局事务也被称作分布式事务
有一个名叫 X/Open 的组织提出了 XA 的规范,用于规范分布式事务,也就是不同的存储引擎只要符合 XA 规范,就可以放到一个分布式事务中管理
XA 规范提出了两个角色
资源管理器:全局事务中每一个小事务
事务管理器:管理全局事务中每一个小事务 (后文称之为协调者)
要提交一个全局事务,就必须保证所有小事务都要能顺利提交,否则所有事务都应该回滚。XA 规范指出,要提交一个全局事务,必须分两步:(点题)
prepare 阶段:每个小事务在该阶段会执行各自的事务,并将事务产生的 redo log 刷新到磁盘中,做好提交的准备
commit 阶段:协调者准备提交前,会询问每个小事务是否做好了提交的准备,如果都准备好了就直接提交,否则全部回滚
在外部 XA 事务中,每个小事务在分布在不同的数据库服务器中。在 MySQL 使用 XA 事务的一些操作如下:
XA {start|begin} xid
:开启一个 XA 事务,此时该 XA 事务处于 active 状态。xid 表示每个事务唯一的 id
XA end xid
:告知服务器有 xid 标识的 XA 事务的所有语句全部输入,此时该 XA 事务处于 idle 状态
XA prepare xid
:应用程序让处于 idle 状态的 XA 事务将产生的 redo log 刷新到磁盘中,做好提交的准备,此时该 XA 事务处于 prepare 状态
XA commit xid [one phase]
:对于处于 prepare 状态的 XA 事务,应用程序可以发送XA commit xid
让服务器提交,此时该 XA 事务处于 commit 状态
XA rollback xid
:应用程序通过该语句让服务器回滚 xid 所标识的 XA 事务,此时该 XA 事务处于 abort 状态
XA recover
:应用程序可以通过该语句查看处于 prepare 状态的 XA 事务
下面给出 XA 事务的状态转换图
在内部 XA 事务中,每个小事务可能分布在同一个数据库服务器的不同存储引擎中,或者在存储引擎和插件之间。下面以最典型的 binlog 和 redo log 展开讨论内部 XA 事务
redo log 以 mtr 为单位添加到 redo log buffer 中,在事务提交时会刷盘,在事务提交前后台线程每隔 1s 刷盘;bilog 以事务为单位先添加到 binlog cache 中,等事务提交后刷新到磁盘中
由于 redo log 是存储引擎生成,binlog 是 server 层生成,无法保证这两个刷盘操作的原子性,而这两个操作必须要保证原子性,否则会出现一致性问题
如果 redo log 刷盘成功,binlog 没有刷盘成功,在主从复制的架构中会导致主库和从库数据不一致,因为从库是根据 binlog 来复制数据
如果 redo log 没有刷盘成功,binlog 刷盘成功,在主从复制的架构中会导致主库和从库数据不一致,因为主库是根据 redo log 来恢复数据
在客户端执行 commit 或者自动提交事务的情况下,MySQL 内部开启一个 XA 事务保证 redo log 和 binlog 刷盘的原子性,binlog 作为协调者,存储引擎作为参与者
从上图可以看出,将 redo log 持久化拆分成两个阶段:prepare 和 commit,binlog 在中间穿插写入
prepare 阶段:先将 XID 写入到 redo log 中,然后将 redo log 刷盘,最后将 redo log 对应的事务设置为 prepare 状态
commit 阶段:先将 XID 写入到 binlog 中,然后将 binlog 刷盘,接着调用存储引擎提交事务接口将 redo log 状态设置为 commit,此状态不需要持久化,可以根据 binlog 中是否存在 XID 判断 redo log 状态
假设时刻 1 (redo log 刷盘成功,binlog 刷盘失败) 或者时刻 2 (redo log 和 binlog 都刷盘成功,但还没有写入 commit 标识) 系统挂了,此时的 redo log 都处于 prapare 阶段
在 MySQL 重启后会重写扫描 redo log 文件,碰到处于 prepare 状态的 redo log,就会去检查 binlog 中是否存在和 redo log 相同的 XID
如果 binlog 中不存在相同 XID,表示 binlog 没有刷盘成功,直接回滚事务 (对应时刻 1)
如果 binlog 中存在相同 XID,表示 binlog 刷盘成功,直接提交事务 (对应时刻 2)
总结:是否回滚事务完全取决于 binlog 是否存在和 redo log 中相同的 XID,如果不存在就需要回滚,否则直接提交即可