Mysql通过LRU链表做缓冲池满了之后的进行淘汰缓存页的策略,那么仅通过LRU机制在实际运行过程中是存在巨大的安全隐患的。

    (1)预读带来的一个巨大问题:
    所谓预读机制:就是当你从磁盘上加载一个数据页的时候,他可能会连带着把这个数据页相邻的其他数据页,也加载到缓存里去。
    假设现在有两个空闲缓存页,然后在加载一个数据页的时候,连带着把他相邻的数据页也加载到了缓存里,实际上只有一个缓存页被访问了,另外一个通过预读机制加载的缓存页,其 实并没有人访问,此时这两个缓存页都在LRU链表的前面。
    然后在这两个缓存页后面的也是被经常访问的,假设没有空闲缓存页了,此时要加载新的数据页,从LRU链表尾部把最近最少使用的缓存页 拿出来刷到磁盘里,然后腾出一个缓存页。这两个缓存页只是一瞬间被加载过来的,尤其是那个通过预读机制加载进来压根没人访问,这样把那个尾部的缓存页刷到磁盘是不合理的。

    (2)哪些情况会触发MySQL的预读机制?
    (A)有一个参数innodb_read_ahead_threshold,他的默认值是56,意思是顺序的访问了一个区域里的多个数据页,访问的数据页的数量超过了这个阈值,此时就会触发预读机制,把 下一个相关区域中的所有数据页都加载到缓存里去。
    (B)如果Buffer Pool里缓存了一个区域里13个连续的数据页,而且这些数据页都是比较频繁被访问的,此时就会直接触发预读机制,把这个区里的其他的数据页都加载到缓存里去, 这个机制是通过innodb_random_read_ahead 来控制的,它默认是OFF的。
    所以默认情况下,主要是第一个规则可能会触发预读机制,一下子把很多相邻区域里的数据页加载到缓存里去,这些缓存页如果一下子都放到了LRU链表的头部,而且没人访问,就会 导致本来就在缓存里的一些频繁被访问的缓存页在LRU链表的尾部,这样尾部被淘汰掉的热点缓存页是不合理的。

    (3)另一种可能导致频繁被访问的缓存页被淘汰的场景
    全表扫描,执行类似的“Select * from table ”此时没加任何一个where条件,会直接导致一下子把表里所有的数据页,都从磁盘加载到Buffer Pool里了,这个时候就会把表的 所有数据页都一一装入各个缓存页里去,此时LRU链表排在前面的一大串缓存页是全表扫描加载进来的,然后全面扫描过后,后续几乎不用这些缓存页数据呢?当Buffer Pool 满了需要淘汰一 些缓存页,那么之前被频繁访问了缓存页就淘汰掉了进行刷入磁盘的操作。

    1. **总结**:如果使用简单的LRU链表机制,其实是漏洞百出的。

    问题为什么MySQL要设计预读这个机制?
    它加载一个缓存页在缓存的时候,为什么把一些相邻的数据页也加载到缓存里去?这样做的意义?是为了应对什么样的场景?

    :其实是为了提升性能,假设你读取了数据页01到缓存页里去,接下来可能顺序读取数据页01相邻的数据页02到缓存里去,这个时候是不是可能在真正要去读取数据页02时候再次发起一次 磁盘IO?
    所以为了优化性能,MySQL设计了预读机制,也就是在一个数据区域内,顺序读取了好多数据页了,比如01-56,MySQL会判断,你可能会接着继续顺序读取后面的数据页,索性给你加载 57-72这块相邻区域的数据页。
    19.png