1、各个链表的作用
- free链表:存放空的缓存页
- flush链表:存放使用到的缓存页
- LRU(least recently uesd)链表:存放所有缓存页,将缓存页的使用次数进行排序,当缓存页不够的时候,不常用的缓存也刷入磁盘,清理出的缓存页也供使用
2、free链表的使用
当数据库启动的时候,mysql会去操作系统申请一块内存空间用来做buffer pool,将磁盘中的数据页缓存在buffer pool中,也就是缓存页,这个时候,缓存页都是空的,因为还没有对数据库进行crud操作,这些缓存页会储存到free链表中,free链表为双向链表。
这个链表中的描述数据为缓存页的描述数据,基础节点用来统计一共有多少缓存页。
将磁盘中的数据页读取到buffer pool中的缓存页时,需要从free链表你获取一个描述数据块,然后就可以对应的获取到这个描述数据块对应的空闲缓存页,同时把一些相关的描述数据写入缓存页的描述数据块中,例如数据页所属的表空间之类的信息,之后把描述数据块从free链表中去除。
3、如何判断数据页有没有被缓存
数据库有一个哈希表数据结构,通过“表空间+数据页号”作为key,然后缓存页的地址作为value,当使用一个数据页的时候,通过“表空间+数据页号”作用key去哈希表中查一下,如果没有就读取数据页,如果已经有了,说明数据页已经被缓存了,就会直接使用。
4、flush链表的使用
flush链表也为双向链表。当执行curd操作的时候,会在free链表中取出一张缓存页,用来记录频繁调用的数据,这些频繁调用的缓存页会变成脏页,存储在flush链表中。
flush链表的结构跟free链表几乎是一样的,如图所示:
5、LUR链表的使用(Least Recently Used)
在高并发的情况下,会发生缓存页不够用的情况,需要将缓存页,或者脏页中的数据刷入磁盘来腾出新的缓存页,刷入磁盘的缓存页必须是不常用的数据或者是查过一次后就不再使用的数据页,为了不影响效率,会将这些数据页存储在LRU链表中。
数据的缓存页,都会放在LRU里,如果最近加载数据的缓存页,会放在LRU链表的头部如图所示:
如果某个缓存页的描述数据块本来在LRU链表的尾部,后续只要查询或者修改了这个缓存页的数据,也要把这个缓存页挪动到LRU链表的头部去,也就是说最近被访问过的缓存页,一定在LRU链表的头部。如下图:
此时LRU链表尾部的缓存页一定是访问最少的缓存页,将LRU尾部的缓存页刷入磁盘腾出空闲的缓存页到free链表。
6、MySQL的预读机制
当从磁盘上加载一个数据页的时候,可能会连带着把这个数据也相邻的其他数据页加载到缓存中
例如:现在有两个空闲缓存页,然后再加载一个数据页的时候,连带着把它相邻的数据页也加载到缓存中,正好每个数据页放入一个空闲缓存页。但实际上只有一个缓存页是被访问了,另外一个通过预读机制加载的缓存页,其实并没有人访问,此时这两个缓存页可都在LRU链表的前面,如下图:
如果将LRU链表尾部的缓存页刷入磁盘,会造成资源浪费。
触发预读机制的条件
1、innodb_read_ahead_threshold默认为56,访问的数量超过这个阈值,触发
2、Buffer pool缓存区的13个连续的数据页被频繁访问,触发预读机制;这个机制由innodb_readom_read_ahead控制,默认为off,关闭
3、全表扫描
7、LRU算法的优化
LRU链表基于冷热数据分离设计
将LRU链表拆为热数据区域,冷数据区域
第一次加载的缓存页放在冷区域的头部,1s后如果再次访问这个数据页时,将它移动到热区域头部
当缓存页不够的情况下,将数据LRU链表的冷区域缓存页释放;
LRU链表算法优化到极致
热区域的缓存页被访问后,前面1/4的缓存页是不会移动到头部,后3/4的数据被访问,会移动到头部。这样尽可能的减少表中的节点移动。
如下图所示:
8、LRU链表尾部缓存页刷入磁盘
- 定时将LRU尾部的部分缓存页刷入磁盘
后台运行定时任务,隔一段时间将LRU链表冷数据区域尾部的缓存页刷入磁盘,加回free链表
- 把flush链表中的一些缓存页刷入磁盘
定时将flush链表中的缓存页刷入磁盘,释放缓存页