innodb二阶段日志提交机制和组提交解析

2018-06-18 00:43:17来源:未知 阅读 ()

新老客户大回馈,云服务器低至5折

前些天在查看关于innodb_flush_log_at_trx_commit的官网解释时产生了一些疑问,关于innodb_flush_log_at_trx_commit参数的详细解释参见官网:

https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_flush_log_at_trx_commit

其中有一段是这么写的:
With a value of 2, the contents of the InnoDB log buffer are written to the log file after each transaction commit and the log file is flushed to disk approximately once per second.
意思是:如果innodb_flush_log_at_trx_commit的值设为2,那么log buffer里的内容会在每次提交时被写入redo log file,然后redo log file每秒被flush到disk。
由于innodb的redo log file据我所知是在硬盘上的ib_logfile,所以对于这里的log file被flush到disk很疑惑,难道log buffer和disk之间还存在了一层可以缓存log file的结构?
 
在查阅了大量中英文资料后,总算有了初步的了解,暂总结于此。
 
一、名词解释
在innodb存储引擎中,有一种独有的log file,即redo log file,因此对于innodb存储引擎来说,就存在两种logfile:redo log和binlog.
redo log:即data目录下的ib_logfile0,ib_logfile1(个数由innodb_log_files_in_group控制),innodb存储引擎特有,在内存中有相应的redo log buffer。
因此写redo时的3层结构为:redo log buffer--->文件系统缓存中的redo logfile--->disk上的redo log file
binlog:默认在data目录下,也可以通过log_bin参数直接指定路径,文件名为默认为<hostname>-bin前缀的文件,在内存中没有log buffer。
因此写binlog时的2层结构为:文件系统缓存中的binlog--->disk上的binlog
好尴尬,Oracle通常是绕过文件系统缓存来直接写入磁盘的,对Mysql的这种机制还不太了解。
 
二、二阶段日志写的流程
原图来自:https://jin-yang.github.io/post/mysql-group-commit.html  (个人认为此站作者对于这幅图的理解有误,以下为查阅资料后的个人解释)
当开启binlog后,如果会话发出了commit的请求,那么在committed之前,一系列的流程为:
1.prepare阶段:
此阶段负责将log buffer的redo日志和undo写入文件系统缓存中的redo log和undo以及写入disk上的redo log和undo,写入机制取决于innodb_flush_log_at_trx_commit参数。
innodb_flush_log_at_trx_commit:(默认值为1)
  • 此值为0表示:redo log buffer的内容每秒会被写入文件系统缓存的redo log里,同时被flush(固化)到disk上的redo log file中。
  • 此值为1表示:redo log buffer的内容会在事务commit时被写入文件系统缓存的redo log里,同时被flush(固化)到disk上的redo log file中。
  • 此值为2表示:redo log buffer的内容会在事务commit时被写入文件系统缓存的redo log里,而文件系统缓存的redo log每秒一次被flush(固化)到disk上的redo log file中。

注意log buffer和undo buffer(也叫undo page)是在事务执行过程中就即时生成的(undo的disk文件位置默认在系统表空间中,5.6以后也可以自己指定独立的表空间),

2.写binlog阶段:
此阶段调用两个方法write()和fsync(),前者负责写文件系统缓存中的binlog,后者负责将文件系统缓存中的binlog写入disk上的bin log,前者在整个事务执行过程中都会被一直调用,后者的调用机制由sync_binlog参数控制。
关于sync_binlog参数:
  • sync_binlog=0:表示fsync()的调用完全交给操作系统,即文件系统缓存中的binlog是否刷新到disk完全由操作系统控制。
  • sync_binlog=1:表示在事务提交时,binlog一定会被固化到disk
  • sync_binlog=N(N>1):数据库崩溃时,可能会丢失N-1个事务,具体原理也详见https://jin-yang.github.io/post/mysql-group-commit.html
3.innodb引擎内部提交阶段:
Innodb完成事务提交,清除undo信息,将事务设置为TRX_NOT_STARTED状态。
Innodb进行crash recovery时是根据binlog来进行前滚回滚的。只有记录了binlog才会根据redo log前滚或回滚事务。
 
三、故障恢复解析
1.如果在一阶段后崩溃,binlog未写,innodb引擎层直接回滚事务。
2.如果在二阶段后崩溃,binlog已写,那么server告诉innodb重做事务,事务不丢失。
在主从复制的情况下如果innodb_flush_log_at_trx_commit不为1则有可能出现binlog已写但是redo log未写的情况,此时主库崩溃后在事务前滚时会出现找不到redo的情况导致前滚失败,而从库已经应用binlog,导致主从不一致。
而sync_binlog不为1则可能出现主库直接丢失事务的情况。
因此,为保证主从完全一致且事务不丢失,主库的innodb_flush_log_at_trx_commit和sync_binlog都必须设置为1。
 
四、Binlog Group Commit
以上提到单个事务的二阶段提交过程,设置正确的innodb_flush_log_at_trx_commit参数值可以保证 InnoDB 和 binlog的一致性,但是在并发的情况下可能对于主从复制需要考虑如下的BUG:

假设有3个顺序执行的事务T1、T2、T3,Innodb层事务按照 T2、T3、T1 顺序提交,但Server层的binlog写入顺序为是T1、T2、T3(binlog不遵循WAL机制,只能猜测也是顺序写的),当 T2、T3在提交事务之后做了一个xtrabackup备份用于新建一个slave来做主从复制,那么搭建备库时由于T3事务已经完成,因此从T3之后开始同步,但是此时有可能丢失T1的binlog,造成主从不一致。

PS:个人感觉网站流传的T1、T2、T3组提交异常的那副图是错的,因此不建议引用。

结论:需要保证 binlog 的写入顺序和 InnoDB 事务提交顺序一致,用于 xtrabackup 备份恢复。

早期解决方案:

早期,使用 prepare_commit_mutex 保证顺序,只有当上一个事务 commit 后释放锁,下个事务才可以进行 prepare 操作,并且在每个事务过程中binlog 没有fsync() 的调用。

由于内存数据写入磁盘的开销很大,如果频繁 fsync() 把日志数据永久写入磁盘,数据库的性能将会急剧下降。为此提供 sync_binlog 参数来设置多少个 binlog 日志产生的时候调用一次 fsync() 把二进制日志刷入磁盘来提高整体性能,prepare_commit_mutex 的锁机制会严重影响高并发时的性能,而且 binlog 也无法执行组提交。

改进方案:

Mysql5.6 引入了组提交,并将提交过程分成 Flush stage、Sync stage、Commit stage 三个阶段。其实简单的说就是加入队列机制使得binlog写入顺序与事务执行顺序一致,加入队列的最大好处就是可以不获取prepare_commit_mutex锁也能实现不降低性能的日志顺序写。

Binlog组提交的基本思想是,引入队列机制保证Innodb commit顺序与binlog落盘顺序一致,并将事务分组,组内的binlog刷盘动作交给一个事务进行,实现组提交目的。在MySQL数据库上层进行提交时首先按顺序将其放入一个队列中,队列中的第一个事务称为leader,其他事务称为follow,leader控制着follow的行为。

  • Flush Stage

1) 持有Lock_log mutex [leader持有,follower等待]。

2) 获取队列中的一组binlog(队列中的所有事务)。

3) 将binlog buffer到I/O cache。

4) 通知dump线程dump binlog。

  • Sync Stage

1) 释放Lock_log mutex,持有Lock_sync mutex[leader持有,follower等待]。

2) 将一组binlog 落盘(sync动作,最耗时,假设sync_binlog为1)。

  • Commit Stage

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的效果越明显,数据库性能的提升也就越大。

与 binlog 组提交相关的参数主要包括了如下两个:

  • binlog_max_flush_queue_time

单位为微秒,用于从 flush 队列中取事务的超时时间,这主要是防止并发事务过高,导致某些事务的 RT 上升,详细内容可以查看函数MYSQL_BIN_LOG::process_flush_stage_queue() 。

注意:该参数在 5.7 之后已经取消了。

  • binlog_order_commits

当设置为 0 时,事务可能以和 binlog 不同的顺序提交,其性能会有稍微提升,但并不是特别明显.

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:MyCAT详解

下一篇:MySQL 5.7.20 Group Relication(组复制)搭建手册