mysql 内部xa(两阶段提交)

原文链接:https://www.linuxidc.com/Linux/2015-11/124942.htm

 

mysql为了保证redo Log 和binlog一致性,内部事物提交采用xa两阶段提交。
注:redo log为引擎层日志,binlog为server层日志。
 
MySQL中的XA实现分为:外部XA和内部XA;前者是指我们通常意义上的分布式事务实现;后者是指单台MySQL服务器中,Server层作为TM(事务协调者),而服务器中的多个数据库实例作为RM,而进行的一种分布式事务,也就是MySQL跨库事务;也就是一个事务涉及到同一条MySQL服务器中的两个innodb数据库(因为其它引擎不支持XA)。
 

XA 将事务的提交分为两个阶段,而这种实现,解决了 binlog 和 redo log的一致性问题,这就是MySQL内部XA的第三种功能。

MySQL为了兼容其它非事物引擎的复制,在server层面引入了 binlog, 它可以记录所有引擎中的修改操作,因而可以对所有的引擎使用复制功能;MySQL在4.x 的时候放弃redo的复制策略而引入binlog的复制(淘宝丁奇)。

但是引入了binlog,会导致一个问题——binlog和redo log的一致性问题:一个事务的提交必须写redo log和binlog,那么二者如何协调一致呢?事务的提交以哪一个log为标准?如何判断事务提交?事务崩溃恢复如何进行?
MySQL通过两阶段提交(内部XA的两阶段提交)很好地解决了这一问题:
第一阶段:InnoDB prepare,持有prepare_commit_mutex,并且write/sync redo log; 将回滚段设置为Prepared状态,binlog不作任何操作;
第二阶段:包含两步,1> write/sync Binlog; 2> InnoDB commit (写入COMMIT标记后释放prepare_commit_mutex);
以 binlog 的写入与否作为事务提交成功与否的标志,innodb commit标志并不是事务成功与否的标志。因为此时的事务崩溃恢复过程如下:
1> 崩溃恢复时,扫描最后一个Binlog文件,提取其中的xid; 
2> InnoDB维持了状态为Prepare的事务链表,将这些事务的xid和Binlog中记录的xid做比较,如果在Binlog中存在,则提交,否则回滚事务。
通过这种方式,可以让InnoDB和Binlog中的事务状态保持一致。如果在写入innodb commit标志时崩溃,则恢复时,会重新对commit标志进行写入;
在prepare阶段崩溃,则会回滚,在write/sync binlog阶段崩溃,也会回滚。这种事务提交的实现是MySQL5.6之前的实现。
 
 binlog 组提交
上面的事务的两阶段提交过程是5.6之前版本中的实现,有严重的缺陷。当sync_binlog=1时,很明显上述的第二阶段中的 write/sync binlog会成为瓶颈,而且还是持有全局大锁(prepare_commit_mutex: prepare 和 commit共用一把锁),这会导致性能急剧下降。解决办法就是MySQL5.6中的 binlog组提交。 
 MySQL5.6中的binlog group commit:

Binlog Group Commit的过程拆分成了三个阶段:

1> flush stage 将各个线程的binlog从cache写到文件中; 

2> sync stage 对binlog做fsync操作(如果需要的话;最重要的就是这一步,对多个线程的binlog合并写入磁盘);

3> commit stage 为各个线程引擎层的事务commit(这里不用写redo log,在prepare阶段已写)。每个stage同时只有一个线程在操作。(分成三个阶段,每个阶段的任务分配给一个专门的线程,这是典型的并发优化)

这种实现的优势在于三个阶段可以并发执行,从而提升效率。注意prepare阶段没有变,还是write/sync redo log.
(另外:5.7中引入了MTS:多线程slave复制,也是通过binlog组提交实现的,在binlog组提交时,给每一个组提交打上一个seqno,然后在slave中就可以按照master中一样按照seqno的大小顺序,进行事务组提交了。)
 
 MySQL5.7中的binlog group commit:

淘宝对binlog group commit进行了进一步的优化,其原理如下:

从XA恢复的逻辑我们可以知道,只要保证InnoDB Prepare的redo日志在写Binlog前完成write/sync即可。因此我们对Group Commit的第一个stage的逻辑做了些许修改,大概描述如下:

 Step1. InnoDB Prepare,记录当前的LSN到thd中; 
 Step2. 进入Group Commit的flush stage;Leader搜集队列,同时算出队列中最大的LSN。 
 Step3. 将InnoDB的redo log write/fsync到指定的LSN  (注:这一步就是redo log的组写入。因为小于等于LSN的redo log被一次性写入到ib_logfile[0|1])
 Step4. 写Binlog并进行随后的工作(sync Binlog, InnoDB commit , etc)

也就是将 redo log的write/sync延迟到了 binlog group commit的 flush stage 之后,sync binlog之前。

通过延迟写redo log的方式,显式的为redo log做了一次组写入(redo log group write),并减少了(redo log) log_sys->mutex的竞争。

也就是将 binlog group commit 对应的redo log也进行了 group write. 这样binlog 和 redo log都进行了优化。

参数innodb_support_xa默认为true,表示启用XA,虽然它会导致一次额外的磁盘flush(prepare阶段flush redo log). 但是我们必须启用,而不能关闭它。因为关闭会导致binlog写入的顺序和实际的事务提交顺序不一致,会导致崩溃恢复和slave复制时发生数据错误。如果启用了log-bin参数,并且不止一个线程对数据库进行修改,那么就必须启用innodb_support_xa参数。

 
posted @ 2018-10-16 16:45  呆呆晶  阅读(2658)  评论(1编辑  收藏  举报