(五)大白话MySQL的Buffer Pool的free链表、flush链表、LRU链表

(一)大白话MySQL执行SQL的流程

(二)大白话InnoDB存储引擎的架构设计

(三)大白话MySQL Binlog是什么?

(四)MySQL的Buffer Pool内存结构

(五)MySQL的Buffer Pool的free链表、flush链表、LRU链表

(六)MySQL是如何基于冷热数据分离的方案,来优化LRU算法?

(七)MySQL是如何将LRU链表的使用性能优化到极致的?

1、数据库启动的时候,是如何初始化Buffer Pool的呢?

前面我们已经讲过MySQL数据库的Buffer Pool到底长成什么样,其实简单点说,Buffer Pool里面就是会包含很多个缓存页,同时每个缓存页还有一个描述数据,也可以叫做是控制数据,但是一般大家都叫做描述数据,或者缓存页的元数据。

那么在数据库启动的时候,他是如何初始化Buffer Pool的呢?

  • 其实这个也很简单,数据库只要一启动,就会按照你设置的Buffer Pool大小,稍微再加大一点,去找操作系统申请一块内存区域,作为Buffer Pool的内存区域。

  • 然后当内存区域申请完毕之后,数据库就会按照默认的缓存页的16KB的大小以及对应的800个字节左右的描述数据的大小,在Buffer Pool中划分出来一个一个的缓存页和一个一个的他们对应的描述数据

然后当数据库把Buffer Pool划分完毕之后,看起来就是之前我们看到的那张图了,如下图所示。
在这里插入图片描述

只不过这个时候,Buffer Pool中的一个一个的缓存页都是空的,里面什么都没有,要等数据库运行起来之后,当我们要对数据执行增删改查的操作的时候,才会把数据对应的页从磁盘文件里读取出来,放入Buffer Pool中的缓存页中。

2、Buffer Pool的free链表

2.1 我们怎么知道哪些缓存页是空闲的呢?

接着来看下一个问题,当数据库运行起来之后,我们的系统肯定会不停的执行增删改查的操作,此时就需要不停的从磁盘上读取一个一个的数据页放入Buffer Pool中的对应的缓存页里去,把数据缓存起来,那么以后就可以在内存里对这个数据执行增删改查了。

但是此时在从磁盘上读取数据页放入Buffer Pool中的缓存页的时候,必然涉及到一个问题,那就是哪些缓存页是空闲的?

因为默认情况下磁盘上的数据页和缓存页是一 一对应起来的,都是16KB,一个数据页对应一个缓存页。数据页只能加载到空闲的缓存页里,所以MySql必须要知道Buffer Pool中哪些缓存页是空闲的状态?

MySQL数据库会为Buffer Pool设计了一个free链表,是一个双向链表数据结构,这个free链表里,每个节点就是一个空闲的缓存页的描述数据块的地址,也就是说,只要你一个缓存页是空闲的,那么它的描述数据块就会被放入这个free链表中。

刚开始数据库启动的时候,所有的缓存页都是空闲的,因为此时可能是一个空的数据库,一条数据都没有,所以此时所有缓存页的描述数据块,都会被放入这个free链表中。

假设数据此时总共有3个数据页,那么它的free链表如下图所示:
在这里插入图片描述

  • 大家可以看到上面出现了一个free链表,这个free链表里面就是各个缓存页的描述数据块,只要缓存页是空闲的,那么他们对应的描述数据块就会加入到这个free链表中,每个节点都会双向链接自己的前后节点,组成一个双向链表

  • 除此之外,这个free链表有一个基础节点,它会引用链表的头节点和尾节点,里面还存储了链表中有多少个描述数据块的节点,也就是有多少个空闲的缓存页。

  • 这里要给大家讲明白一点,这个free链表,其实就是咱们所学的数据结构中的双向链表。它本身其实就是由Buffer Pool里的描述数据块组成的,你可以认为是每个描述数据块里都有两个指针,一个是free_pre,一个是free_next,分别指向自己的上一个free链表的节点,以及下一个free链表的节点

  • 通过Buffer Pool中的描述数据块的free_pre和free_next两个指针,就可以把所有的描述数据块串成一个free链表。上面为了画图需要,假如数据库总共有3个空闲的描述数据块,展示他们之间的指针引用关系。

  • 对于free链表而言,只有一个基础节点是不属于Buffer Pool的,它是40字节大小的一个节点,里面就存放了free链表的头节点的地址,尾节点的地址,还有free链表里当前有多少个节点。

2.2 如何将磁盘上的页读取到Buffer Pool的缓存页中去?

好了,有了free链表,当需要把磁盘上的数据页读取到Buffer Pool中的缓存页里去的时候,是怎样一个操作过程呢?

其实有了free链表之后,这个操作就很简单了。

  • 首先,我们需要从free链表里获取一个描述数据块,然后就可以获取到这个描述数据块对应的空闲缓存页;

  • 接着我们就可以把磁盘上的数据页读取到对应的缓存页里去,同时把相关的一些描述数据写入缓存页的描述数据块里去,比如这个数据页所属的表空间之类的信息,如下图所示:
    在这里插入图片描述

  • 最后把那个描述数据块从free链表里去除就可以了。移除后,链表变化如下图所示:

  • 在这里插入图片描述

2.3 MySQL怎么知道某个数据页有没有被缓存?

接着我们来看下一个问题:MySQL怎么知道某个数据页有没有被缓存?

我们在执行增删改查的时候,肯定是先看看这个数据页有没有被缓存,如果没被缓存就走上面的逻辑,从free链表中找到一个空闲的缓存页,从磁盘上读取数据页写入缓存页,写入描述数据,从free链表中移除这个描述数据块。

但是如果数据页已经被缓存了,那么就会直接使用了。所以其实数据库还会有一个哈希表数据结构,他会用表空间号+数据页号,作为一个key,然后缓存页的地址作为value。当你要使用一个数据页的时候,通过“表空间号+数据页号”作为key去这个哈希表里查一下,如果没有就读取数据页,如果已经有了,就说明数据页已经被缓存了,如下图所示:
在这里插入图片描述
MySQL引入了一个数据页缓存哈希表的结构,也就是说,每次你读取一个数据页到缓存之后,都会在这个哈希表中写入一个key-value对,key就是表空间号+数据页号,value就是缓存页的地址,那么下次如果你再使用这个数据页,就可以从哈希表里直接读取出来它已经被放入一个缓存页了。

3、Buffer Pool的flush链表

3.1 为什么会有脏数据页?

前面的章节我们也讲过,你要更新的数据页都会在Buffer Pool的缓存页里,供你在内存中直接执行增删改的操作。比如前面讲的如下更新操作:

update users set name='lisi' where id=2

mysql肯定会去更新Buffer Pool的缓存页中的数据,此时一旦更新了缓存页中的数据,那么缓存页里的数据和磁盘上的数据页里的数据,是不是就不一致了?

这个时候,我们就说缓存页是脏数据,脏页,如下图。这个前面已经讲的很明白,这里不做过多重复讲解。
在这里插入图片描述

3.2 脏页怎么刷回磁盘呢?

我们都是知道一点的,最终这些在内存里更新的脏页的数据,都是要被刷新回磁盘文件的。

但是这里就有一个问题了,不可能所有的缓存页都刷回磁盘的,因为有的缓存页可能是因为查询的时候被读取到Buffer Pool里去的,可能根本没修改过!那mysql是怎么判断哪些缓存页需要刷回磁盘的呢?

所以mysql数据库在这里引入了另外一个跟free链表类似的flush链表。

flush链表也是由描述数据块组成,凡是被修改过的缓存页,都会把它的描述数据块加入到flush链表中去。flush的意思就是这些都是脏页,后续都是要flush刷新到磁盘上去的。

所以flush链表的结构如下图所示,跟free链表几乎是一样的,只不过是脏页对应的描述数据块组成的链表,这里就不详细介绍了。
在这里插入图片描述

好多人看上图认为flush链表是把描述数据库又复制了一份,这是错误的!

flush链表和free链表类似,这里方便展示,单独复制一份描述数据,只是方便大家理解。其实内存中仅有一份描述数据。

3.3 flush链表的总结

讲到这里,大家都应该明白了,当msyql更新缓存页的时候,通过变换缓存页中的描述数据块的flush链表的指针,就可以把脏页的描述数据块组成一个双向链表,也就是flush链表,而且flush链表的基础节点会指向起始节点和尾巴节点。

通过这个flush链表,就可以记录下来哪些缓存页是脏页了!

4、Buffer Pool的LRU链表

4.1 当Buffer Pool中缓存页不够了怎么办?

之前我们已经给大家讲解了Buffer Pool中的缓存页的划分,包括free链表的使用,然后磁盘上的数据页是如何加载到缓存页里去的,包括对缓存页修改之后,形成内存里的脏页,flush链表是如何用来记载脏数据页的。

我们接着来分析Buffer Pool的工作原理,首先来思考一个问题,当你要执行CRUD操作的时候,无论是查询数据,还是修改数据,实际上都会把磁盘上的数据页加载到缓存页里来,这个大家都是没有问题的吧?

那么在加载数据到缓存页的时候,必然是要加载到空闲的缓存页里去的,所以必须要从free链表中找一个空闲的缓存页,然后把磁盘上的数据页加载到那个空闲的缓存页里去。

那么大家通过之前的学习肯定都知道了,随着你不停的把磁盘上的数据页加载到空闲的缓存页里去,free链表中的空闲缓存页是不是会越来越少?因为只要你把一个数据页加载到一个空闲缓存页里去,free链表中就会减少一个空闲缓存页。

所以,当你不停的把磁盘上的数据页加载到空闲缓存页里去,free链表中不停的移除空闲缓存页,迟早有那么一瞬间,你会发现free链表中已经没有空闲缓存页了。

这个时候,当你还要加载数据页到一个空闲缓存页的时候,怎么办呢?如下图所示:
在这里插入图片描述

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

针对上述的问题,大家来思考下一个问题,如果所有的缓存页都被塞了数据了,此时无法从磁盘上加载新的数据页到缓存页里去了,那么此时你只有一个办法,就是淘汰掉一些缓存页。

那什么叫淘汰缓存页呢?

顾名思义,你必须把一个缓存页里被修改过的数据,给他刷到磁盘上的数据页里去,然后这个缓存页就可以清空了,让他重新变成一个空闲的缓存页。

接着你再把磁盘上你需要的新的数据页加载到这个腾出来的空闲缓存页中去,如下图。

在这里插入图片描述

那么下一个问题来了,如果要把一个缓存页里的数据刷入磁盘,腾出来一个空闲缓存页,那么应该把哪个缓存页的数据给刷入磁盘呢?

4.3 缓存命中率概念

要解答这个问题,我们就得引入一个缓存命中率的概念。

  • 假设现在有两个缓存页,一个缓存页的数据,经常会被修改和查询,比如在100次请求中,有30次都是在查询和修改这个缓存页里的数据。那么此时我们可以说这种情况下,缓存命中率很高,为什么呢?因为100次请求中,30次都可以操作缓存,不需要从磁盘加载数据,这个缓存命中率就比较高了。

  • 另外一个缓存页里的数据,就是刚从磁盘加载到缓存页之后,被修改和查询过1次,之后100次请求中没有一次是修改和查询这个缓存页的数据的,那么此时我们就说缓存命中率有点低,因为大部分请求可能还需要走磁盘查询数据,他们要操作的数据不在缓存中。

所以针对上述两个缓存页,假设此时让你做一个抉择,要把其中缓存页的数据刷入到磁盘去,腾出来一个空闲的缓存页,此时你会选择谁?

那还用想么,当然是选择第二个缓存页刷入磁盘中了!因为第二个缓存页,压根儿就没什么人来使用他里面的数据,结果这些数据还空占据了一个缓存页,这不是白白浪费缓存页吗?

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

接着我们就要解决下一个问题了,就是你怎么知道哪些缓存页经常被访问,哪些缓存页很少被访问?

此时就要引入一个新的LRU链表了,这个所谓的LRU就是Least Recently Used,最近最少使用的意思。

通过这个LRU链表,我们可以知道哪些缓存页是最近最少被使用的,那么当你缓存页需要腾出来一个刷入磁盘的时候,不就可以选择那个LRU链表中最近最少被使用的缓存页了么

这个LRU链表大致是怎么个工作原理呢?

简单来说,我们看下图,假设我们从磁盘加载一个数据页到缓存页的时候,就把这个缓存页的描述数据块放到LRU链表头部去,那么只要有数据的缓存页,他都会在LRU里了,而且最近被加载数据的缓存页,都会放到LRU链表的头部去。
在这里插入图片描述

然后假设某个缓存页的描述数据块本来在LRU链表的尾部,后续你只要查询或者修改了这个缓存页的数据,也要把这个缓存页挪动到LRU链表的头部去,也就是说最近被访问过的缓存页,一定在LRU链表的头部,如下图所示:
在这里插入图片描述

那么这样的话,当你的缓存页没有一个空闲的时候,你是不是要找出来那个最近最少被访问的缓存页去刷入磁盘?此时你就直接在LRU链表的尾部找到一个缓存页,它一定是最近最少被访问的那个缓存页!

然后你就把LRU链表尾部的那个缓存页刷入磁盘中,然后把你需要的磁盘数据页加载到腾出来的空闲缓存页中就可以了!

100、创作不易,更多精品大白话章节,请订阅本专栏,谢谢支持

IT社团出品,必属精品!预计本专栏有100+章节,持续更新中。。。
在这里插入图片描述
部分目录如下:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值