知识卡片
内部XA事务:用两阶段提交同步存储引擎与二进制日志
内容
MySQL的可插拔存储引擎架构(见[[MySQL可插拔存储引擎架构把查询处理与数据存储彻底解耦]])
带来一个隐藏的一致性问题:存储引擎彼此完全独立、互不知晓对方的存在,
如果一个事务同时涉及多个存储引擎,逐个顺序提交就无法保证”要么全部
提交、要么全部不提交”——只要在提交过程中某个存储引擎恰好崩溃,事务
的原子性就被破坏了。更关键的是,即使只涉及单一存储引擎(比如只用
InnoDB),MySQL自己维护的二进制日志(binlog)也要被当成一个独立的
“参与者”:一次提交实际上要同时让InnoDB的事务日志和binlog都记录”已
提交”,这本质上就是一个分布式事务,只不过参与者是MySQL内部的两个
子系统而已。为了保证这两边要么都持久化成功、要么都不算数,MySQL内部
借助XA协议做两阶段提交:先让InnoDB把事务标记为”预提交”(prepare,
写入InnoDB事务日志但不算正式提交),再写入binlog,最后再让InnoDB把
预提交的事务正式标记为已提交——只要中途崩溃,恢复时可以靠”binlog里
有没有这条记录”来判断该把InnoDB里预提交的事务回滚还是继续提交,从而
让两边保持一致。这个正确性保证的代价很直接:正常情况下一次事务提交
只需要一次磁盘刷盘(fsync),开启这套内部XA协调后,至少需要三次
fsync(InnoDB预提交一次、binlog一次、InnoDB正式提交一次),这也是
MySQL 5.0引入该机制后打破了此前”批量提交”优化、导致明显性能下降的
直接原因。关闭binlog或关闭innodb_support_xa能避开这些额外的fsync,但代价是
既不安全(崩溃后可能出现存储引擎与binlog不一致的悬空提交),复制功能
也无法正常工作,因此这是一个只有在能接受这些代价时才该做的权衡,而非
默认推荐项。
结构图:
sequenceDiagram
participant App as 应用发起提交
participant InnoDB as InnoDB事务日志
participant Binlog as 二进制日志(binlog)
App->>InnoDB: 1.预提交(prepare)<br/>fsync写InnoDB日志
App->>Binlog: 2.写入binlog<br/>fsync写binlog
App->>InnoDB: 3.正式提交(commit)<br/>fsync标记已提交
Note over InnoDB,Binlog: 崩溃恢复时:<br/>binlog有该事务记录→InnoDB继续提交<br/>binlog无该事务记录→InnoDB回滚预提交