(一)大白话MySQL执行SQL的流程
(二)大白话InnoDB存储引擎的架构设计
(三)大白话MySQL Binlog是什么?
(四)MySQL的Buffer Pool内存结构
(五)MySQL的Buffer Pool的free链表、flush链表、LRU链表
(六)MySQL是如何基于冷热数据分离的方案,来优化LRU算法?
(七)MySQL是如何将LRU链表的使用性能优化到极致的?
本人正好从事过MYSQL运维十几年,所以本专栏从零开始、一步一图,全部使用大白话,即使0基础的小白也看得懂得语言,系统全面的讲解mysql底层实现、mysql如何通过优化获取最佳行呢,只要坚持学完本专栏,您也能成为MySQL实战优化高手!
前一节我们借助于一条更新SQL讲解了MySQL的常用存储引擎Innodb是怎么去完成这条数据更新,明白了Innodb存储引擎有几个重要的组件:Buffer Pool、Undo日志和Redo日志。
update users set name='lisi' where id=2
数据更新过程如下图:
那么MySQL除了以上的两种主要日志文件外,还有一种重要的日志文件:binlog。本文会一步一图来带你了解binlog为什么是mysq的核心组件之一。
binlog是一个二进制格式的文件,主要有以下几个作用:
接着上一讲讲到,在我们提交事务的时候,会把redo log日志写入磁盘文件中去。但是其实在提交事务的时候,我们同时还会把这次更新对应的binlog日志写入到磁盘文件中去,如下图所示:
大家可以在这个图里看到一些变动,就是把跟InnoDB存储引擎进行交互的组件加了进来,这个组件就是之前提过的执行器,他会负责跟InnoDB进行交互,包括从磁盘里加载数据到Buffer Pool中进行缓存,包括写入undo日志,包括更新Buffer Pool里的数据,以及写入redo log buffer,redo log刷入磁盘,写binlog,等等。
实际上,执行器是非常核心的一个组件,负责跟存储引擎配合完成一个SQL语句在磁盘与内存层面的数据全部更新操作。
而且我们在上图可以看到,每一次更新语句的执行,拆分为了两个阶段:
对于binlog日志,其实和redo日志一样也有不同的刷盘策略,有一个sync_binlog参数可以控制binlog的刷盘策略。
所以跟之前分析的一样,如果此时机器宕机,那么你在os cache里的binlog日志是会丢失的,我们看下图的示意:
那么这样提交事务之后,哪怕机器宕机,磁盘上的binlog是不会丢失的,如下图所示
总结:
当我们把binlog写入磁盘文件之后,接着就会完成最终的事务提交,此时会把本次更新对应的binlog文件名称和这次更新的binlog日志在文件里的位置,都写入到redo log日志文件里去,同时在redo log日志文件里写入一个commit标记。
在完成这个事情之后,才算最终完成了事务的提交,我们看下图的示意。
这时候肯定有童鞋会问了,最后在redo日志中写入commit标记有什么意义呢?
说白了,这个commit其实是用来保持redo log日志与binlog日志一致性的。
我们来举个例子,假设我们在提交事务的时候,一共有上图中的5、6、7三个步骤,必须是三个步骤都执行完毕,才算是提交了事务。那么在我们刚完成步骤5的时候,也就是redo log刚刷入磁盘文件的时候,mysql宕机了,此时怎么办?
这个时候因为没有最终的事务commit标记在redo日志里,所以此次事务可以判定为不成功。不会说redo日志文件里有这次更新的日志,但是binlog日志文件里没有这次更新的日志,不会出现数据不一致的问题。
如果要是完成步骤6的时候,也就是binlog写入磁盘了,此时mysql宕机了,怎么办?
同理,因为没有redo log中的最终commit标记,因此此时事务提交也是失败的。
必须是在redo log中写入最终的事务commit标记了,然后此时事务提交成功,而且redo log里有本次更新对应的日志,binlog里也有本次更新对应的日志 ,redo log和binlog完全是一致的。
现在我们假设已经提交事务了,此时一次更新“update users set name=‘lisi’ where id=2”,他已经把内存里的buffer pool中的缓存数据更新了,同时磁盘里有redo日志和binlog日志,都记录了把我们指定的“id=2”这行数据修改了“name=‘lisi’”。
此时我们会思考一个问题了,但是这个时候磁盘上的数据文件里的“id=2”这行数据的name字段还是等于"zhangsan"这个旧的值!那么什么时候会把磁盘的数据刷成"lisi"呢?
其实MySQL有一个后台的IO线程,会在之后某个时间里,随机的把内存buffer pool中的修改后的脏数据给刷回到磁盘上的数据文件里去,我们看下图:
当上图中的IO线程把buffer pool里的修改后的脏数据刷回磁盘的之后,磁盘上的数据才会跟内存里一样,都是"name=lisi"这个修改以后的值了!
在你IO线程把脏数据刷回磁盘之前,哪怕mysql宕机崩溃也没关系,因为重启之后,会根据redo日志恢复之前提交事务做过的修改到内存里去,就是id=10的数据的name修改为了"lisi"。然后等适当时机,IO线程自然还是会把这个修改后的数据刷到磁盘上的数据文件里去的。
好多人可能说了,binlog日志和redo日志都是还原数据库数据用的,这两个功能很类似,是不是存在功能重复的问题?相信很多人都混淆过,其实两者的本质不同。
大家通过一次更新数据的流程,就可以清晰地看到,InnoDB存储引擎主要就是包含了一些buffer pool、redo log buffer等内存里的缓存数据,同时还包含了一些undo日志文件、redo日志文件等东西,同时mysql server自己还有binlog日志文件。
在你执行更新的时候,每条SQL语句,都会对应修改buffer pool里的缓存数据、写undo日志、写redo log buffer几个步骤。但是当你提交事务的时候,一定会把redo log刷入磁盘,binlog刷入磁盘,完成redo log中的事务commit标记。最后后台的IO线程会随机的把buffer pool里的脏数据刷入磁盘里去。
其实大家可以看到MySQL通过两阶段提交过程来完成事务的一致性的,也即redo log和binlog的一致性的。理论上是先写redo log,再写binlog,两个日志都提交成功(刷入磁盘),事务才算真正的完成。