1.LRU链表

mysql中引入了一个LRU链表,这个LRU就是Least Recently Used,最近最少使用的意思。
从磁盘上加载一个数据页到缓存页的时候,就会把这个数据页的描述数据块放入LRU链表中头部去。只要有数据的缓存页,都会被加载到LRU链表中,并且最近被加载数据的缓存页,都会放到LRU链表头部。如果此时缓存页没有一个是空闲的,那么LRU尾部的缓存页一定是最近最少使用的。
04_LRU算法淘汰部分缓存 - 图1

2.LRU链表可能导致的问题

2.1.预读导致的问题

所谓预读机制,就是当从磁盘加载一个数据页的时候,可能会把这个数据页相邻的数据页也加载到缓存中去。实际上,可能只有一个缓存页被访问到了,其他的缓存页并没有被访问,此时,这两个缓存页都在LRU链表前面。
04_LRU算法淘汰部分缓存 - 图2
如果此时没有空闲缓存页,此时要加载新数据页,就会把LRU链表尾部的缓存页清除掉,但是尾部的缓存页是有访问的,第二个缓存页是通过预读机制加载进来的,没有任何访问,显然这样的清除不合理。

2.2.哪些情况下会触发MySQL的预读机制

  1. 参数innodb_read_ahead_threshold,默认值56,意思是如果顺序的访问了一个区的多个数据页,访问的数据页超过了这个阈值,就会触发预读机制,把下一个相邻区的所有数据页加载到缓存页中。
  2. 如果Buffer Pool里缓存了一个区里的13个连续的数据页,而且这些数据页都是比较频繁访问的,就会触发预读机制,把这个区的其他数据页都加载到缓存中。这个机制是通过参数innodb_random_read_ahead控制的,默认是OFF,关闭的。
  3. 全表扫描,会一下子把所有数据页加载到Buffer Pool中。

    3.基于冷热分离的思想设计LRU链表

    真正的LRU链表,会被拆分为两个部分,一部分是热数据,一部分是冷数据。冷热数据的比例是由innodb_old_blocks_pct参数控制的,默认是37,也就是冷数据占比37%。
    04_LRU算法淘汰部分缓存 - 图3
    数据页第一次被加载到缓存的时候,缓存页会被放到冷数据区域的链表头部。
    mysql中设计了一个innodb_old_blocks_time参数,默认值1000。一个数据页被加载到缓存页1s之后,访问这个缓存页,缓存页才会从冷数据区域链表头部移到热数据区域链表头部;如果数据页被加载到缓存页1s之内访问了这个缓存页,是不会把这个缓存页移到热数据区域的头部的。
    只有在热数据区域的后3/4部分的缓存页被访问了,才会把缓存页移动到链表头部。这样可以减少链表中的节点移动,减少对性能的影响。

    4.定时把LRU链表尾部的部分缓存刷入磁盘

    并不是缓存页满的时候,才会将冷数据区域尾部的几个缓存页刷入磁盘,而是有一个后台线程,会每隔一段时间就会把LRU链表的冷数据区域尾部的一些缓存页,刷入到磁盘里,清空这几个缓存页,把他们放到free链表。
    04_LRU算法淘汰部分缓存 - 图4