从磁盘读取数据页到Buffer Pool的时候,free链表有什么用?
我们怎么知道那些缓存是空闲的?
当我们数据库运行起来的时候,肯定会不断的做增删改查,将磁盘上读取一个一个数据页放入Buffer Pool中对应的缓存页里去
但是从磁盘数据页放入Buffer Pool中的缓存页的时候,必然涉及到一个问题,那些缓存页是空闲的?
数据库会设计一个free链表,一个双向链表,这个free链表里,每个节点就是一个空闲的缓存页描述数据块的地址,只要你一个缓存页是空闲的,那么他的描述数据块就会放入这个free链表中

free链表中占用了多少内存空间?
这个free链表,他本身就是由Buffer Pool里的描述数据块组成的,可以认为是每个描述数据块里都有两个指针,一个是free_pre,一个是free_next
对于free链表而言,只有一个基础节点是不属于Buffer Pool的,他是40字节大小的一个节点,里面存放着链表头节点的地址,尾节点的地址,还有free链表里当前有多少个节点
如何将磁盘上的页读取到Buffer Pool缓存页中去?
只要将free链表里获取一个描述数据块,然后就可以对应的获取足够描述数据块对应的空闲缓存页

怎么知道数据页有没有被缓存?
数据库有一个哈希表数据结构,他会用表空间号+数据页号,作为一个key,然后缓存页的地址作为value
当你使用一个数据页的时候,通过"表空间号+数据页号"作为这个key去这个哈希表里检查一下,如果没有就读取数据页,如果有就代表存在

当我们更新Buffer Pool数据时,flush链表有什么用?
脏数据页为什么脏?
我们要更新的数据页都会在Buffer Pool的缓存页里,共我们在内存执行增删改操作
接下来去更新Buffer Pool缓存页中的数据,此时一旦更新了缓存页中的数据,那么缓存页里的数据和磁盘上的数据页里的数据,就会不一致,这个就叫做脏数据,脏页

哪些缓存页是脏页?
我们之前学过,最终在内存里更新的脏页的数据,都是要被刷新回磁盘文件的
但是不可能所有缓存页都刷回磁盘的,因为有的缓存页可能因为查询的时候被读取到Buffer Pool里去,可能没有被修改
所有数据库在这里需要引入和free表类似的flush链表,其本质就是通过缓存页的数据块中的两个指针,让被修改的缓存页的数据块,组成一个双向链表
凡是被修改的缓存页,都会把他的描述数据块加入到flush链表中,flush意思是脏数据,后续都要刷新flush到磁盘

当Buffer Pool的缓冲页不够的时候,如何基于LRU算法淘汰部分内存?
当我们执行CRUD的时候,无论是查询数据还是修改数据,实际上都会把磁盘的数据页加载到缓存页里来。
那么在加载数据到缓存页的时候,必然是要加载到空闲的缓存页中,所有必须要从free链表中找一个空闲的缓存页,然后把磁盘上的缓存页加载到那个空闲的缓存页里去。

随着不停把磁盘上的数据页加载到空闲的缓存页中,free链表中的空闲缓存页会越来越少,迟早有一刻缓存页会满
这时候还要加载一个空闲的缓存页该怎么办?

针对这个问题,我们就需要使用淘汰缓存页了

缓存命中率概念引入
假设现在有两个缓存页,一个缓存页的数据,经常被修改和查询,比如命中100次请求,有30次请求都在查询和修改这个缓存页中的数据,这样就是这个缓存命中率高
另外一个,查询和修改在100次请求中,就命中1个,那么其命中率就很低
现在如果我们要腾出空间来选择一个,我们肯定会选择第二种方式
LRU算法
我们怎么知道哪些缓存经常被访问,哪些缓存很少被访问?
这时候就需要LRU链表,也就是最近最少使用的意思

简单的LRU算法会遇到哪些问题?
预读机制
首先会带来隐患的是MySQL的预读机制,当你从磁盘上加载一个数据页的时候,他可能会连带着把这个数据页相邻的其他数据页也加载到缓存当中

哪些情况下会触发MySQL的预读机制?
- 有一个参数
innodb_read_ahead_threshold,默认是56,如果顺序访问一个区里的多个数据页,访问的数据页的数量超过了这个阈值,此时就会触发预读机制,把下一个相邻区域中的所有数据页都加载到缓存里面去 - 如果Buffer Pool里缓存了一个区里的13个连续的数据页,而且这些数据页都是比较频繁被访问的,此时就会发送预读机制,把这个区域中的数据加到缓存中,这个机制是由
innodb_random_read_ahead来控制的,默认是OFF,也就是关闭的意思
另外一种可能导致频繁被访问的缓存页被淘汰的场景
全表扫描,会导致一下子将这个表所有的数据页都从磁盘加载到Buffer Pool里面去,这时候会导致以前经常访问的缓存页都被淘汰了
MySQL是如何基于冷热数据分离方案,来优化LRU算法的?
冷热数据分离
前面我们说明了简单的LRU算法带来的问题,而MySQL肯定也考虑到了这一点,所以他们使用的是冷热数据分离的思想
真正的LRU链表是拆分为两部分,一部分是热数据,一部分是冷数据,这个比例是由innodb_old_blocks_pct参数控制的,默认是37%,也就是冷数据占37%

运行流程
第一次加载一个缓存页的时候,其会先放到冷数据链表的头部
MySQL里面设定了一个参数innodb_old_blocks_time,默认为1000,也就是说一个数据页被加载到缓存页后,1s之后访问这个缓存页,他才会被挪动到热数据链表的头部
解决之前的问题
前面我们说到全部扫描,这时候不就迎刃而解,当全表扫描的时候,会将其全部放到冷数据头部的位置,而不影响热数据