10.BufferPool之预读与预读失效

228 阅读8分钟

InnoDB管理缓冲页算法

最容易想到的,就是LRU(Least recently used)。

画外音:memcache,OS都会用LRU来进行页置换管理,但MySQL的玩法并不一样。

传统的LRU是如何进行缓冲页管理?

最常见的玩法是,把入缓冲池的页放到LRU的头部,作为最近访问的元素,从而最晚被淘汰。这里又分两种情况:

(1)页已经在缓冲池里,那就只做“移至”LRU头部的动作,而没有页被淘汰;

(2)页不在缓冲池里,除了做“放入”LRU头部的动作,还要做“淘汰”LRU尾部页的动作;

image.png

如上图,假如管理缓冲池的LRU长度为10,缓冲了页号为1,3,5…,40,7的页。

假如,接下来要访问的数据在页号为4的页中:

image.png

(1)页号为4的页,本来就在缓冲池里;

(2)把页号为4的页,放到LRU的头部即可,没有页被淘汰;

画外音:为了减少数据移动,LRU一般用链表实现。

假如,再接下来要访问的数据在页号为50的页中:

image.png

(1)页号为50的页,原来不在缓冲池里;

(2)把页号为50的页,放到LRU头部,同时淘汰尾部页号为7的页;

什么是预读?

磁盘读写,并不是按需读取,而是按页读取,一次至少读一页数据(一般是16K) ,如果未来要读取的数据就在页中,就能够省去后续的磁盘IO,提高效率。

所谓预读机制,说的就是当你从磁盘上加载一个数据页的时候,他可能会连带着把这个数据页相邻的其他数据页,也加载到缓存bufferpool里去! 预读请求的所有页集中在一个范围内(区)。

举个例子,假设现在有两个空闲缓存页,然后在加载一个数据页的时候,连带着把他的一个相邻的数据页也加载到缓存里去了,正好每个数据页放入一个空闲缓存页!

触发MySQL的预读机制

InnoDB使用两种预读算法来提高I/O性能:线性预读(linear read-ahead)和随机预读(randomread-ahead)

为了区分这两种预读的方式,我们可以把线性预读放到以extent为单位,而随机预读放到以extent中的page为单位

线性预读着眼于将下一个extent提前读取到buffer pool中。

随机预读着眼于将当前extent中的剩余的page提前读取到buffer pool中。

线性预读

(1) 有一个参数是innodb_read_ahead_threshold,他的默认值是56,意思就是如果顺序的访问了一个区里的多个数据页,访问的数据页的数量超过了这个阈值,此时就会触发预读机制,把下一个相邻区中的所有数据页都加载到缓存里去。

线性预读方式有一个很重要的变量控制是否将下一个extent预读到buffer pool中,通过使用配置参数innodb_read_ahead_threshold控制触发innodb执行预读操作的时间。

如果一个extent中的被顺序读取的page超过或者等于该参数变量时,Innodb将会异步的将下一个extent读取到buffer pool中。可以设置为0-64的任何值(因为一个extent中也就只有64页),默认值为56,值越高,访问模式检查越严格。

例如,如果将值设置为48,则InnoDB只有在顺序访问当前extent中的48个pages时才触发线性预读请求,将下一个extent读到内存中。如果值为8,InnoDB触发异步预读,即使程序段中只有8页被顺序访问。

随机预读

(2) 如果Buffer Pool里缓存了一个区里的N个连续的数据页,而且这些数据页都是比较频繁会被访问的,此时就会直接触发预读机制,把这个区里的其他的数据页都加载到缓存里去。N的默认值是13。

这个机制是通过参数innodb_random_read_ahead来控制的,由于随机预读方式给innodb code带来了一些不必要的复杂性,同时在性能也存在不稳定性,在5.5中已经将这种预读方式废弃,默认是OFF。

所以默认情况下,主要是线性预读可能会触发预读机制,一下子把很多相邻区里的数据页加载到缓存里去,这些缓存页如果一下子都放在LRU链表的前面,而且他们其实并没什么人会访问的话,那就会如上图,导致本来就在缓存里的一些频繁被访问的缓存页在LRU链表的尾部。

哪些情况会触发MySQL预读机制

现在我们已经理解了预读机制一下子把相邻的数据页加载进缓存,放入LRU链表前面的隐患了,预读机制加载进来的缓存页可能根本不会有人访问,结果他却放在了LRU链表的前面,此时可能会把LRU尾部的那些被频繁访问的缓存页刷入磁盘中!

所以我们来看看,到底哪些情况下会触发MySQL的预读机制呢?

(1) 有一个参数是innodb_read_ahead_threshold,他的默认值是56,意思就是如果顺序的访问了一个区里的多个数据页,访问的数据页的数量超过了这个阈值,此时就会触发线性预读机制,把下一个相邻区中的所有数据页都加载到缓存里去。

(2) 如果Buffer Pool里缓存了一个区里的13个连续的数据页,而且这些数据页都是比较频繁会被访问的,此时就会直接触发随机预读机制,把这个区里的其他的数据页都加载到缓存里去

这个机制是通过参数innodb_random_read_ahead来控制的,他默认是OFF,也就是这个规则是关闭的

所以默认情况下,主要是第一个规则可能会触发预读机制,一下子把很多相邻区里的数据页加载到缓存里去,这些缓存页如果一下子都放在LRU链表的前面,而且他们其实并没什么人会访问的话,那就会如上图,导致本来就在缓存里的一些频繁被访问的缓存页在LRU链表的尾部。

这样的话,一旦要把一些缓存页淘汰掉,刷入磁盘,腾出来空闲缓存页,就会如上所述,把LRU链表尾部一些频繁被访问的缓存页给刷入磁盘和清空掉了!这是完全不合理的,并不应该这样!

预读为什么有效?

数据访问,通常都遵循“集中读写”的原则,使用一些数据,大概率会使用附近的数据,这就是所谓的“局部性原理”,它表明提前加载是有效的,确实能够减少磁盘IO。

什么是预读失效?

由于预读(Read-Ahead),提前把页放入了缓冲池,但最终MySQL并没有从页中读取数据,称为预读失效。

举个例子,假设现在有两个空闲缓存页,然后在加载一个数据页的时候,连带着把他的一个相邻的数据页也加载到缓存里去了,正好每个数据页放入一个空闲缓存页!

但是接下来呢,实际上只有第1个缓存页是被访问了,第2个通过预读机制加载的缓存页,其实并没有人访问,此时这两个缓存页可都在LRU链表的最前面。

image.png

前两个缓存页都是刚加载进来的,但是此时第二个缓存页是通过预读机制捎带着加载进来的,他也放到了链表的前面,但是他实际没人访问他。

除了第2个缓存页之外,第1、3、4个缓存页都是一直有人访问的缓存页。

这个时候,假如没有空闲缓存页了,那么此时要加载新的数据页了,是不是就要从LRU链表的尾部把所谓的“最近最少使用的第4个缓存页”给拿出来,刷入磁盘,然后腾出来一个空闲缓存页了?

这个时候,如果你把上图中第4个缓存页刷入磁盘然后清空,你觉得合理吗?

他可是之前一直频繁被人访问的啊!只不过在这一个瞬间,被新加载进来的两个缓存页给占据了LRU链表前面的位置,尤其是第二个缓存页,居然还是通过预读机制加载进来的,根本就不会有人访问!

如果你要是把LRU链表尾部的缓存页给刷入磁盘,这是绝对不合理的,最合理的反而是把上图中LRU链表的第二个通过预读机制加载进来的缓存页给刷入磁盘和清空,毕竟他几乎是没什么人会访问的!

参考:blog.csdn.net/qq_34436819…

如何判断预读是否失效

通过两个状态值,评估预读算法的有效性

show global status like '%read_ahead%';

image.png

  • 1.Innodb_buffer_pool_read_ahead:通过预读(后台线程)读入innodb buffer pool中数据页数
  • 2.Innodb_buffer_pool_read_ahead_evicted:通过预读来的数据页没有被查询访问就被清理的pages,无效预读页数