1、如果Buffer Pool中的缓存页不够了怎么办?

当执行CRUD操作的时候,无论是查询数据,还是修改数据,实际上都会把磁盘上的数据页加载到空闲的缓存页,所以必须要从free链表中找一个空闲的缓存页,然后把磁盘上的数据页加载到那个空闲的缓存页里去。
随着不停的把磁盘上的数据页加载到空闲的缓存页里去,free链表中的空闲缓存页会越来越少,因为只要把一个数据页加载到一个空闲缓存页里去,free链表中就会减少一个空闲缓存页。 当不停的把磁盘上的数据页加载到空闲缓存页里去,free链表中不停的移除空闲缓存页,迟早有那么一瞬间,free链表没有空闲缓存页了 ,这个时候,当你还要加载数据页到一个空闲缓存页的时候,怎么办呢?

2、如果要淘汰掉一些缓存数据,淘汰谁?

如果所有的缓存页都被塞了数据了,无法从磁盘上加载新的数据页到缓存页里去了,此时只有一个办法,就是淘汰掉一些缓存页。
必须把一个缓存页里被修改过的数据,刷到磁盘上的数据页里去,然后这个缓存页就可以清空了,让它重新变成一个空闲的缓存页。 接着再把磁盘上需要的新的数据页加载到这个腾出来的空闲缓存页中去。
如果要把一个缓存页里的数据刷入磁盘,腾出来一个空闲缓存页,那么应该把哪个缓存页的数据给刷入磁盘呢?

3、缓存命中率概念的引入

假设现在有两个缓存页:

  • 一个缓存页的数据,经常会被修改和查询,比如在100次请求中,有30次都是在查询和修改这个缓存页里的数据。那么此时可以说这种情况下,这个缓存命中率就比较高了。
  • 另外一个缓存页里的数据,就是刚从磁盘加载到缓存页之后,被修改和查询过1次,之后100次请求中没有一次是修改和查询这个缓存页的数据的,那么此时就说缓存命中率有点低,因为大部分请求可能还需要走磁盘查询数据,要操作的数据不在缓存中。

4、引入LRU链表来判断哪些缓存页是不常用的

那么怎么知道哪些缓存页经常被访问,哪些缓存页很少被访问? 此时就要引入一个新的LRU(Least Recently Used,最近最少使用)链表。通过这个LRU链表,可以知道哪些缓存页是最近最少被使用的,那么当缓存页需要腾出来一个刷入磁盘的时候,可以选择那个LRU链表中最近最少被使用的缓存页了。

LRU链表工作原理:

  • 从磁盘加载一个数据页到缓存页的时候,就把这个缓存页的描述数据块放到LRU链表头部去;
  • 最近被加载数据的缓存页,都会放到LRU链表的头部去,这样在LRU链表的尾部,一定是最近最少被访问的那个缓存页。

5、简单的LRU链表在Buffer Pool实际运行中,可能导致哪些问题?

  1. 预读带来的一个巨大问题

MySQL的预读机制,当从磁盘上加载一个数据页的时候,可能会连带着把这个数据页相邻的其他数据页,也加载到缓存里去。接下来,实际上只有一个缓存页是被访问了,另外一个通过预读机制加载的缓存页,其实并没有人访问,此时这两个缓存页可都在LRU链表的前面。
image.png
这个时候,要是把LRU链表尾部的缓存页给刷入磁盘,这是绝对不合理的。

6、哪些情况下会触发MySQL的预读机制?

  1. 有一个参数是innodb read ahead_threshold,他的默认值是56,意思就是如果顺序的访问了一个区里的多个数据页,访问的数据页的数量超过了这个阈值,此时就会触发预读机制,把下一个相邻区中的所有数据页都加载到缓存里去 ;
  2. 如果Buffer Pool里缓存了一个区里的13个连续的数据页,而且这些数据页都是比较频繁会被访问的,此时就会直接触发预读机制,把这个区里的其他的数据页都加载到缓存里去 ;

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

7、另外一种可能导致频繁被访问的缓存页被淘汰的场景

-全表扫描 类似如下的SQL语句:SELECT*FROM USERS,没加任何一个where条件,会导致直接一下子把这个表里所有的数据页,都从磁盘加载到Buffer Pool里去。
此时可能LRU链表中排在前面的一大串缓存页,都是全表扫描加载进来的缓存页。LRU链表的尾部,可能全部都是之前一直被频繁访问的那些缓存页。
然后当要淘汰掉一些缓存页腾出空间的时候,就会把LRU链表尾部一直被频繁访问的缓存页给淘汰掉了,而留下了之前全表扫描加载进来的大量的不经常访问的缓存页!