为什么要有缓存?
- 我们知道每次获取数据我们都需要从磁盘获取,磁盘的运行速度又慢的不行,对于这一个问题我们要怎么解决呢?
- 我们把查询结果存储起来不就行了,因为当需要访问某个页的数据时,就会把完整的页的数据全部加载到内存中,也就是说即使我们只需要访问一个页的一条记录,那也需要先把整个页的数据加载到内存中。所以在进行完这些页对应的操作之后,不释放,而是将他们存储起来,下次再访问这个页的时候,就可以直接从缓存里面取,这就是缓存的意义
InnoDB 的缓存 Buffer Pool
什么是Buffer Pool?
- 设计InnoDB的大佬为了缓存磁盘中的页,在MySQL服务器启动的时候就向操作系统申请了一片连续的内存,他们给这片内存起了个名,叫做Buffer Pool(中文名是缓冲池)。
Buffer Pool 是由什么组成的?
- Buffer Pool 是缓存池,肯定是缓存数据页,所以我们的基础数据元肯定是跟数据页结构一样,叫缓存页,但是为了更加方便地管理和找到对应的缓存页,我们还需要给每个页加一个控制块。具体结构如下:
- 控制块里面存储的是页的信息,包括表空间编号、页号、缓存页在Buffer Pool中的地址、链表节点信息、一些锁信息以及LSN信息等等,控制块和缓存页是一一对应的,它们都被存放到 Buffer Pool 中,其中控制块被存放到 Buffer Pool 的前面,缓存页被存放到 Buffer Pool 后边
- 碎片区,每一个控制块都对应一个缓存页,那在分配足够多的控制块和缓存页后,可能剩余的那点儿空间不够一对控制块和缓存页的大小,自然就用不到喽,这个用不到的那点儿内存空间就被称为碎片了
Free 链表管理
- 背景:当我们最初启动MySQL服务器的时候,需要完成对Buffer Pool的初始化过程,就是先向操作系统申请Buffer Pool的内存空间,然后把它划分成若干对控制块和缓存页。但是此时并没有真实的磁盘页被缓存到Buffer Pool中(因为还没有用到),之后随着程序的运行,会不断的有磁盘上的页被缓存到Buffer Pool中。
- 问题:从磁盘上读取一个页到Buffer Pool中的时候该放到哪个缓存页的位置呢?或者说怎么区分Buffer Pool中哪些缓存页是空闲的,哪些已经被使用了呢?
- 解决:这个时候就需要有个结构记录一下这些没有被使用的缓存也,这个时候控制块就派上大用场了,我们可以把所有空闲的缓存页的控制块放到一个链表中,每次需要的时候,取链表的第一个就可以了,而这个链表就被称之为 free 链表
- 简介:从图中可以看出,我们为了管理好这个free链表,特意为这个链表定义了一个基节点,里边儿包含着链表的头节点地址,尾节点地址,以及当前链表中节点的数量等信息。这里需要注意的是,链表的基节点占用的内存空间并不包含在为Buffer Pool申请的一大片连续内存空间之内,而是单独申请的一块内存空间。
- 作用:有了这个free链表之后事儿就好办了,每当需要从磁盘中加载一个页到Buffer Pool中时,就从free链表中取一个空闲的缓存页,并且把该缓存页对应的控制块的信息填上(就是该页所在的表空间、页号之类的信息),然后把该缓存页对应的free链表节点从链表中移除,表示该缓存页已经被使用了
缓存页管理
- 背景:当我们需要访问某个页中的数据时,就会把该页从磁盘加载到Buffer Pool中,如果该页已经在Buffer Pool中的话直接使用就可以了
- 问题:我们怎么知道该页在不在Buffer Pool中呢?难不成需要依次遍历Buffer Pool中各个缓存页么?
- 解决:我们其实是根据表空间号 + 页号来定位一个页的,也就相当于表空间号 + 页号是一个key,缓存页就是对应的value,怎么通过一个key来快速找着一个value呢?那肯定是希表喽
Flush 链表管理
- 背景:因为磁盘上的数据不会一层不变的,假设我们修改了磁盘上页的数据,刚好这个页也在缓存池中
- 问题:如果缓存池中的数据页发生了修改怎么办?
- 解决:第一种方法就是每次修改就直接同步到磁盘上,这样会严重影响程序的性能,第二种就是而是在未来的某个时间点进行同步
- 问题:当然第二种方法更好,mysql也是选择第二种方式,但是又有个问题,如果不立即同步到磁盘的话,那之后再同步的时候我们怎么知道Buffer Pool中哪些页是脏页,哪些页从来没被修改过呢?
- 解决:我们不得不再创建一个存储脏页的链表,凡是修改过的缓存页对应的控制块都会作为一个节点加入到一个链表中,因为这个链表节点对应的缓存页都是需要被刷新到磁盘上的,所以也叫flush链表。
LRU 算法
- 背景:Buffer Pool对应的内存大小毕竟是有限的
- 问题:如果需要缓存的页占用的内存大小超过了Buffer Pool大小,怎么办呢?
- 解决:将一些旧的缓存页移除,把新的缓存页放进来
问题1 如何找出那些旧的缓存页
- 当Buffer Pool中不再有空闲的缓存页时,就需要淘汰掉部分最近很少使用的缓存页。不过,我们怎么知道哪些缓存页最近频繁使用,哪些最近很少使用呢?
- 我们可以使用一个链表