MySQL之LRU链表详解
概述
在之前的文章中详细讲解了MySQL中BufferPool是什么样的,讲了free链表、flush链表以及lru链表,本篇文章将会着重l讲解ru链表。
一、预读写机制所造成的缺陷
什么是预读写机制呢?就是你在加载一个数据页到缓存中来的时候,会连带着把相邻的数据页也加载进来,就像下面这样:
通过这个图片可能一下看不出来有什么问题,但是如果你想一下这个问题就会发现不对劲的地方
假如此时没有空闲的缓存页了,LRU链表需要腾出一些来,此时把尾部的缓存页给刷入磁盘或者清理掉合适吗?
很明显,这不合适!因为前面有预加载进制所加载进来的缓存页且并没有被访问过,这种缓存页才是应该位于链表尾部等待刷入磁盘或者清空的
触发读写机制的时机?
1. 当一个区内被访问的数据页的数量达到阈值时,就将会把下一个相邻区的数据页都加载进来,这个阈值可以通过参数innodb_read_ahead_threshould进行设置,默认值是56
2. 如果Buffe Pool缓存了一个区的数据,然后这个区里连续13个数据页都被访问了,那么就认为这个区很可能是访问比较频繁的,因此会将这个区内的其他数据页都加载进来
二、基于冷热数据分离的LRU链表
看过上面的问题,大家可能在想如何避免。你觉得应该要把预加载机制禁止吗?答案显然不是,预加载机制被设计出来的目的就是为了提升性能,依据的是空间局部性原理
——当你访问某个数据的时候很可能也会访问相邻的数据。
MySQL中的LRU链表其实是基于冷热数据分离的思想,其实就是将LRU链表分成热数据和冷数据两个部分,并且比例是可以通过参数innodb_old_blocks_pct
进行调整,默认是37,表示冷数据占比37%。
LRU链表其实是这样子的
现在我们结合一下预加载机制来看一下时怎么样的:
当我们从磁盘加载数据页的时候,会将使用的数据和预加载的数据他们放到冷数据区的头部。缓存页是什么时候被放到热数据区的呢?是在被立刻访问的时候吗?如果是的那有没有可能就只是刚加载到内存的时候使用比较频繁,后面就无人问津了呢?
MySQL的做法是这样的:
对于冷数据区,当一个数据页被加载到内存 1000ms
后,如果再次被访问,就会将其加入到热数据区的头部,这个时间可以通过参数innodb_old_blocks_time
进行设置,默认值是1000,单位是ms
对于热数据区,当他被访问的时候不是立刻就将其放到热数据区头部,而是会做一个判断其是否处于链表的前1/3,如果是,那就不会移动,如果处于后2/3的区域才会将其移动,因为每一次一定都会消耗CPU资源。
三、LRU链表中的数据页什么时候会被刷入磁盘
结合之前讲过的free链表和flush链表的作用,接下来将会分析这些链表在日常操作的时候是如何变化的。
当一个新的数据页被加载到缓存页的时候,会将其对应的缓存页从free链表中移除,会将其加入到lru链表冷数据区头部
当这个数据页被修改的时候,会将其加入到flush链表中,在lru链表中可能会从冷数据区被移动到热数据区头部
随着MySQL执行CRUD的次数的增多,可用空间会不断的减少,当free链表没有缓存页的时候会发生什么呢?
首先他会根据LRU链表去确定哪些缓存也是可以腾出来的,如果这个缓存页在flush链表中,那么就会将其刷入磁盘,否则就会将其清空,不用刷入磁盘。然后就会将空闲的缓存页加入到free链表中。现在就是这样一个动态的过程——LRU链表和flush链表增加的时候free链表不断的减少。
现在有一个新的问题?一定要等到缓存页不够用的时候才将LRU链表尾部的数据也清空吗?这样其实是不合适的,在MySQL中,会有一个定时任务,在空闲的时候会将冷数据区尾部的一些缓存页刷入磁盘,以备不时之需。