MySQL Redo/Binlog Group Commit , 2pc事务两阶段提交,Crash Recovery浅析

导读

最近一直在看一些关于mysql innodb事务提交,innodb crash recovery,内部XA事务等资料,整理一下我对这块的一个初步理解。但愿看完后能对你们理解mysql innodb事务提交过程,以及如何作crash recovery有个大概的了解html

2PC

为了性能考虑,每次提交事务的时候,只须要将redo和undo落盘就表明事务已经持久化了,而不须要等待数据落盘。这样就已经能保证事务的crash时的前滚或者回滚。因为undo的信息也会写入redo,因此其实咱们只须要根据redo是否落盘而决定crash recovrey的时候是重作仍是回滚。而上面提到,开启binlog后,还须要考虑binlog是否落盘(binlog牵扯到主从数据一致性,全备恢复的位点)。根据事务是否成功写binlog决定事务的重作仍是回滚。mysql

2PC即innodb对于事务的两阶段提交机制。当mysql开启binlog的时候,会存在一个内部XA的问题:事务在存储引擎层(redo)commit的顺序和在binlog中提交的顺序不一致的问题。sql

2PC原理

将事务的commit分为prepare和commit两个阶段:
一、prepare阶段:redo持久化到磁盘(redo group commit),并将回滚段置为prepared状态,此时binlog不作操做。数据库

clipboard.png

二、commit阶段:innodb释放锁,释放回滚段,设置提交状态,binlog持久化到磁盘,而后存储引擎层提交并发

clipboard.png

对2PC有了一个了解以后,咱们能够发现上面图中redo和binlog均可以group commit,那么下面了解下什么是group commit运维

Group Commit

背景

咱们知道,日志的写入基本上是顺序IO。WAL(Write-Ahead-Logging)用顺序的日志写入代替数据的随机IO实现事务持久化。可是尽管如此,每次事务提交都须要日志刷盘,仍然受限于磁盘IO。group commit的出现就是为了将日志(redo/binlog)刷盘的动做合并,从而提高IO性能分布式

2PC机制只能解决单个事务的redo/binlog顺序一致的问题,若是并发怎么办呢(有可能存在一种状况:binlog提交顺序(T1,T2,T3),innodb commit顺序(T2,T3,T1))。高并发

clipboard.png

如上图,事务按照T一、T二、T3顺序开始执行,将二进制日志(按照T一、T二、T3顺序)写入日志文件系统缓冲,调用fsync()进行一次group commit将日志文件永久写入磁盘,可是存储引擎提交的顺序为T二、T三、T1。当T二、T3提交事务以后,若经过在线物理备份进行数据库恢复来创建复制时,由于在InnoDB存储引擎层会检测事务T3在上下两层都完成了事务提交,不须要在进行恢复了,此时主备数据不一致性能

MySQL 5.6版本以前,经过prepare_commit_mutex锁以串行的方式来保证MySQL数据库上层二进制日志和Innodb存储引擎层的事务提交顺序一致,而后会致使group commit没法生效优化

clipboard.png

上图所示MySQL开启Binary log时使用prepare_commit_mutex和sync_log保证二进制日志和存储引擎顺序保持一致,prepare_commit_mutex的锁机制形成高并发提交事务的时候性能很是差并且二进制日志也没法group commit。

Redo Group Commit

2PC中的prepare阶段,会对redo进行一次刷盘操做(innodb_flush_log_at_trx_commit=1),这时候redo group commit的过程以下:

  1. 获取 log_mutex
  2. 若flushed_to_disk_lsn>=lsn,表示日志已经被刷盘,跳转5
  3. 若 current_flush_lsn>=lsn,表示日志正在刷盘中,跳转5后进入等待状态
  4. 将小于LSN的日志刷盘(flush and sync)
  5. 退出log_mutex

这个过程是根据LSN的顺序进行合并的,也就是说一次redo group commit的过程可能会讲别的未提交事务中的lsn也一并刷盘

Redo Group Commit优化

每一个事务提交时,都会触发一次redo flush动做,因为磁盘读写比较慢,所以很影响系统的吞吐量。淘宝作了一个优化,将prepare阶段的刷redo动做移到了commit(flush-sync-commit)的flush阶段以前,保证刷binlog以前,必定会刷redo。这样就不会违背原有的故障恢复逻辑。移到commit阶段的好处是,能够不用每一个事务都刷盘,而是leader线程帮助刷一批redo。如何实现,很简单,由于log_sys->lsn始终保持了当前最大的lsn,只要咱们刷redo刷到当前的log_sys->lsn,就必定能保证,将要刷binlog的事务redo日志必定已经落盘。经过延迟写redo方式,实现了redo log组提交的目的,并且减小了log_sys->mutex的竞争。目前这种策略已经被官方mysql5.7.6引入。

Binlog Group Commit

Binlog Group Commit的基本思想是引入队列机制,保证innodb commit的顺序与binlog落盘的顺序一致,并将事务分组,组内的binlog刷盘动做交给一个事务进行,实现组提交目的。队列中的第一个事务称为leader,其余事务称为follower。全部事情交给leader去作。过程分为三个阶段

Flush Stag
将每一个事务的binlog写入内存
1) 持有Lock_log mutex [leader持有,follower等待]。
2) 获取队列中的一组binlog(队列中的全部事务)。
3) 将binlog buffer到I/O cache。
4) 通知dump线程dump binlog。

Sync Stage
将内存中的二进制日志刷新到磁盘,若队列中有多个事务,那么仅一次fsync操做就完成了二进制日志的写入,这就是BLGC。
1) 释放Lock_log mutex,持有Lock_sync mutex[leader持有,follower等待]。
2) 将一组binlog 落盘(sync动做,最耗时,假设sync_binlog为1)。

Commit Stage
leader根据顺序调用存储引擎层事务的提交,Innodb自己就支持group commit,所以修复了原先因为锁prepare_commit_mutex致使group commit失效的问题。
1) 释放Lock_sync mutex,持有Lock_commit mutex[leader持有,follower等待]。
2) 遍历队列中的事务,逐一进行innodb commit。
3) 释放Lock_commit mutex。
4) 唤醒队列中等待的线程。

因为有多个队列,每一个队列各自有mutex保护,队列之间是顺序的,约定进入队列的一个线程为leader,所以FLUSH阶段的leader多是SYNC阶段的follower,可是follower永远是follower。

当有一组事务在进行commit阶段时,其余新事物能够进行Flush阶段,从而使group commit不断生效。group commit的效果由队列中事务的数量决定,若每次队列中仅有一个事务,那么可能效果和以前差很少,甚至会更差。但当提交的事务越多时,group commit的效果越明显,数据库性能的提高也就越大。

还有一点要说明:sync_binlog=1这个参数单位表示的是一组事务,而并不是一个事务

Crash Recovery

Crash Recovery(no binlog)

因为未提交的事务和已回滚的事务也会记录到redo log中,所以在进行恢复的时候,这些事务要进行特殊的处理

innodb的处理策略是:进行恢复时,从checkpoint开始,重作全部事务(包括未提交的事务和已回滚的事务),而后经过undo log回滚那些未提交的事务

XA Crash Recovery

一、扫描最后一个 binlog,提取xid(标识binlog中的第几个event)
二、xid也会写到redo中,将redo中prepare状态的xid,去跟最后一个binlog中的xid比较 ,若是binlog中存在,则提交,不然回滚

为何只扫描最后一个binlog?由于binlog rotate的时候会把前面的binlog都刷盘,并且事务是不会跨binlog的

参考资料

MySQL Group Comit - 运维那点事
MySQL 外部XA及其在分布式事务中的应用分析
MySQL undo,redo,2PC,恢复思惟导图
MYSQL-GroupCommit
MySQL的Crash Safe和Binlog的关系 - 老叶茶馆

相关文章
相关标签/搜索