问:你了解MySQL的两阶段提交不?说说看

答:简单来说两阶段提交就是将事务提交分成两部分,经过这两步处理后,我们称这个事务被提交了。
可以看下面这张图:
image.png
我们当执行commit命令时,会按照如下进行。
第一阶段:写redo log,并标识上prepare。
— 中间穿插写binlog —
第二阶段:写redo log,完成事务的最终提交。


插入问题:为什么事务的提交要分成两个阶段?

答:上图中的两阶段是redolog-prepare、redolog-commit两个阶段,在这两个阶段中又写了binlog。
redolog主要提供的能力是对事务进行rollback、并且redolog是存储引擎层面记录的日志。
binlog的作用是方便我们进行数据的备份以及MySQL集群主从之间的数据同步使用,并且binlog是MySQL上层,也就是Server层面记录的日志。

综上:MySQL的两阶段提交是为了保证redolog、binlog两者在逻辑上的一致性。进一步才能保证事务的回滚、数据备份、以及MySQL集群之间的数据是安全可靠的。


插入问题:能不能举例子更直观的阐述,redolog、binlog两者在逻辑一致的必要性以及用途?

答:
例子1:

大型面试:Mysql的组提交 - 图2
在这个例子中,进行完第三步之后,没来得及写binlog,然后主库就挂了,事务也没来得及提交。
这时主库重新启动之后会选择将这个事务丢弃,因为如果它将事务提交了,从库们相对主库来说就少了这条数据,造成了主从数据不一致问题。

例子2:

大型面试:Mysql的组提交 - 图3

在本例中主库宕机前虽然也没有完成对事务对提交,但它已经写了redolog-prepare、还写了binlog,binlog写完之后很可能从库已经收到这个最新的binlog,并且将这个binlog中的数据回放到自己身上了。
所以主库重新启动之后,会选择按照redolog将宕机前的事务状态从内存中恢复出来,也就是说把内存中的缓存页改成脏数据页,然后提交事务。
这时主库如果不提交事务,那主库就比从库少了一条数据,同时会造成主从不一致的情况出现。

问:写日志都是跟磁盘打交道的,Mysql对磁盘的IO性能方面有什么优化方案吗?

  1. 问题详细描述:上面事务两阶段提交的图,事务提交时写了好几次日志。而且一般线上数据库都有这两个配置:
sync_binlog=1
# 该参数控制binlog的落盘时机
# 设置为1表示当事务被提交时将binlog落盘
# 设置为0表示由文件系统自己控制binlog的落盘时机
innodb_flush_log_at_trx_commit=1
# 该参数控制redolog的落盘时机  
# 设置为1表示当事务被提交时将redolog落盘

那你有没有想过:即使写日志是磁盘的顺序IO速度极快,即使固态硬盘的性能很牛,那终究也还是在跟磁盘打交道。那你是否了解MySQL针对这个现状有什么优化的落地实现方案吗?

答:组提交(group commit)。
在MySQL早期的设计中,所谓的组提交实际上提交的是group commit redolog(而不是binlog)。
而且MySQL写redolog并不是产生一条redolog就写一条redo log的!而是以redo log block为单位写入磁盘,而且在redo log block 之上还有个redo log buffer的概念。

总结来说就是:写redolog要分成2大步:
  • Step1:将redolog写入内存buffer中。
  • Step2:将redolog fsync到磁盘中。


    组提交的概念是说:当进行fsync时,一下子将多个事务日志写入到磁盘中。 通俗来讲,假如说你写一次磁盘需要10ms,如果我们按条每个redolog都写一次磁盘的话,10条redolog就需要100ms的耗时。
    有了组提交,可以一次fsync将10条redolog一次磁盘IO就持久化到磁盘中,耗时可能也就十几ms的样子。性能是有所提升的。


插入问题:原来的group commit针对的是redolog,而不是binlog。这个缘由你能展开说一下吗?

答:这个问题就得从binlog的作用说起了。MySQL不可能无缘无故的就写binlog,它之所以记录binlog是因为它想获取到写binlog带给它的好处:给了MySQL搭建集群的可能性,以及数据备份的能力。
而默认情况下binlog是不会开启的。其实说白了,如果你不需要binlog带给我们的能力,比如你偏偏就不需要做数据的备份、或者搭建集群,那么其实你直接使用默认配置不开启binlog就好了。此时的两阶段提交也不复存在。
但是事实并不是这样的,线上的数据库都是以集群的形式存在、并且也需要数据备份。也就是说binlog是开启的状态。事务的提交也是走的两阶段提交。
回到我们组提交的话题,其实在早期的MySQL中,并没有一个很好的机制让组提交两阶段提交共存!根本原因就在于 binlog是MySQL的上层(Server层)记录的。而redolog是下层的存储引擎层记录的。
所以你突然整一个redolog的组提交出来,那两阶段提交怎么办?你为了性能一下将内存中的redolog全落盘了,但是两阶段提交中每一步写日志的顺序是有要求的。它俩正好相违背。
所以其实在InnoDB1.2版本之前,当开启binlog使用两阶段提交时,组提交会失效….


插入问题:那后来没有比较好的解决方式让组提交和两阶段提交共存呢?

答:MySQL5.6及以上版本,它就在两阶段提交的概念上,还加持上了组提交的buff,性能相对之前有了很多的提升。具体的实现可以看这张图:
大型面试:Mysql的组提交 - 图4

总体来说分为三个部分,分别是Flush阶段、Sync阶段、Commit阶段。并且它引入了一个新的概念:队列,事务按提交的顺序进入队列中,队列有先进先出的概念,最先进入队列的事务被成为leader。

  • Flush阶段:将当前队列中的所有事务的binlog统一写入内存中,
  • Sync阶段:将当前队列中的所有事务的binlog通过一次fsync统一写入到磁盘中。(相当于批量将binlog落盘)
  • Commit阶段:由队列中的leader根据一定的顺序调用存储引擎层的事务提交,完成两阶段的第二阶段提交。事务结束…

这时就会形成一个比较理想的状态,当一组事务发生Commit时,新的事务可以去Flush。让整个过程中的组提交不断的生效。

但是如果每次队列中都只有一个事务,那可能会导致性能还不如之前没有这种队列的设计。

还有就是我还了解关于组提交相关的一个控制参数 binlog_max_flush_queue_time,可以控制Flush阶段的等待时间。默认情况下该参数设置为0,推荐设置也为0。如果我们不把它设置为0,意思就是说想让队列中的事务攒一攒,攒多了再统一处理,想获取到一次fsync将大量日志落盘带来的速度快的好处。但是这样同样会导致事务的响应时间变慢。