我们知道mysql有自己的缓存buffer pool,那么就有几个比较重要的问题:
- 数据是如何加载到对应的buffer pool中的,
- 缓存满了如何淘汰数据
- 如何进行脏数据刷盘
下面三大法宝就是这几个问题的解决方案
- free链表
- lru链表
- flush链表
free链表
我们知道磁盘里的数据页是16kb,buffer pool里的数据页也是16kb,那么磁盘里的数据页如果要加载到buffer pool里,它应该放在哪个位置呢?buffer pool里哪个位置的数据页是空闲的呢?其实这些信息是通过free链表记录的。
我们分别采用事前、事中、事后的形式并配合两张图来描述
mysql刚启动
也就是mysql刚启动的时候,buffer pool里还没有数据,大致情况是这样的
我们知道buffer pool里面每个数据页对应一个数据页描述文件,其实free链表也并没有额外的存储,而是通过指针将这些数据页描述文件串联起来,形成一个双向链表,多了一个free链表头的数据结构是额外的存储。只要在这个链表里面的数据页都是空闲的,没有被加载数据的。
加载数据页
如果此时数据要加载到buffer pool里,则去free链表里找最后一个节点,里面的描述文件存储了,数据页的基本信息,包括数据页的起始地址,这样我们就知道将磁盘的数据页加载到buffer pool的哪个位置了,最后就是将数据描述文件从链表移除,注意这里移除并不是删除这个数据页描述文件,而是删除指针而已。
加载完成
最后情况大概是下面这个样子。
通过这样我们就能完美的解决第一个问题。接下来看看第二个问题。
LRU链表
buffer pool的大小是有限的,那么这个时候需要考虑如何淘汰缓存,一般做法是lru算法,也即是 least recently used 淘汰最近最少使用的。同free链表类似,我们需要通过指针将已经装载的数据页串联起来,并且将最近访问的移到队列头。
抽象出来大致如下图所示
将这个图放回buffer pool该有的地方是这样
以上是一个简单LRU算法在buffer pool中的应用,其实真实情况下,mysql的预读机制会导致简单lru算法的一些问题。
mysql在加载数据页到buffer中的时候,不是一页一页的加载,而是加载那一页的附近的几页都加载到buffer pool中,达到预读的作用。为啥要这么搞呢?因为程序的局部性原理呀!!!这是很典型的原理,简简单单的几个字,里面有很大的学问,这里就不展开讲了,可以自己百度下哦。
我们设想一下,如果此时lru队列里有4个节点
其中前面3个节点是经常访问的
假设此时一个查询语句过来,从磁盘加载2个数据页到buffer pool,此时只用到其中一个数据页,那么需要将3和4刷入磁盘,从磁盘加载5、6替换3和4,然后把5、6移动到lru链表前面,最后变成这个样子
不知道大家发现问题木有,实际上数据页描述文件6不是最近访问的,5、1、2是最近访问的,然而1、2却在6的后面,那么下次淘汰数据页页就会先淘汰1、2,显然这样是不合理滴。
那么mysql如何解决这个问题呢?答案:冷热数据分离
假设我们的lru链表是这样滴,前面2个节点是热点数据区,后面3个节点是冷数据区
这个时候一个查询语句过来,将4、5淘汰出去,加载6、7到buffer pool,这时6、7不放入链表的前面,而是放在冷数据区的最前面。
这个时候,最近访问的节点1、2没有被移动到链表后面,完美的解决了之前的问题。那么有人会问,新加载出来的数据,在冷数据区前面,会不会被移动到热点数据区啊?什么时候会被移动呢?
答案是,mysql的做法是如果数据加载后,1秒内,被访问了,则把它移动到热点数据区前面去。假设此时数据页描述文件6被加载后,并被访问了,则6移动到热点数据区最前面。
最后变着这样子
没有被访问的数据页描述文件7,虽然被加载了,但是没有被访问,还是留着队列的后面,是不是很完美的解决,预读造成的问题,不得不说这个解决方案很牛批。这就是冷热分离的LRU算法。
FLUSH链表
下面我们来复习下FLUSH链表是如何进行脏数据刷盘的吧。
未完待续。。。。