Buffer Pool 结构 (2).png

1. 什么是Buffer Pool

Buffer Pool 本质其实就是数据库的一个内存组件,有大小。Buffer Pool 默认情况下128MB,根据实际生产环境下对Buffer Pool 进行调整。可以利用如下的方法,将其设置为2GB。
[server]
innodb_buffer_pool_size=2147483648

2. 如何初始化Buffer Pool

数据库只要一启动,就会按照你设置的Buffer Pool 大小,稍微再加大一点,去找操作系统申请一块内存区域,作为buffer Pool的内存区域。
当内存区域申请完毕之后,数据库就会按照默认的缓存页的16KB的大小以及对应的800个字节左右的描述数据的大小,在Buffer Pool中划分出来一个一个的缓存页和一个个的他们对应的描述数据。并且此时缓存页都是空的,等待从磁盘中加载。
要理解什么是缓存页,就要先理解数据页。

3. 数据页

数据页是MySQL 对数据抽象出来了一个概念,它把很多行数据放在了一个数据页里。
磁盘中存放的数据页大小是16KB,也就是说,一页数据包含了16KB的内容。
而Buffer Pool 中存放的是一个个数据页,通常叫做缓存页,因为毕竟Buffer Pool 是一个缓冲池,里面的数据都是从磁盘到内存去的。
而Buffer Pool 中默认的情况下,一个缓存页的大小和磁盘上的一个数据页的大小一一对应起来的,都是16KB。如果要更新一行数据,此时数据库会找到这行数据所在的数据页,然后加载到Buffer Pool中。
对于每个缓存页,都有一个描述信息,这个描述信息大体可以认为是用来描述这个缓存页的。比如包含如下的东西:这个数据页所属的表空间、数据页的编号,这个缓存页在Buffer Pool 中的地等。
在Buffer Pool 中,每个缓存页的数据在最前面。大概占缓存页大小的5% 左右,大概800个字节左右。

4. free 链表

那么加载的时候怎么知道哪些缓存页是空闲的呢。
Buffer Pool 设计一个free 链表,它是一个双向链表的数据结构,这个free链表里,每个节点就是一个空闲得到缓存页的描述数据块的地址,也就是说,只要你一个缓存页是空闲的,那么他的描述数据块就会被放入这个free 链表中。
刚开始数据库启动的时候,可能所有的缓存页都是空闲的,因为此时可能是一个空的数据,一条数据都没有,此时所有的缓存页的描述块,都会被放入这个free 链表中。
除此之外,这个free 链表有一个基础节点,他会引用链表的头结点和尾节点,里面还存储了链表中有多个描述数据块的节点,也就是有多个空闲的缓存页。
free 链表本身其实由 Buffer Pool 里的描述数据块组成的,你可以认为每个描述数据块都有两个指针,一个是free_pre,一个是free_next,分别指向自己的上一个free 链表的节点,以及下一个free 链表的节点。
通过buffer pool 中描述数据块的Free_pre 和 free-next 两个指针,就可以把所有的描述数据块串成一个free 链表。
对于free 链表而言,只有一个基础节点是不属于Buffer Pool的,他是40字节大小的一个节点,里面存放了free 链表得到头结点的地址,尾节点的地址,还有free 链表里的当前有多少个节点。
有了free 链表,我们只需要从free 链表里获取一个描述数据块,然后就可以对应的获取到这个描述数据块对应的空闲缓存页。接着我们就可以把磁盘上的数据页读取到对应的缓存页里去。同时把相关的一些描述数据写入缓存页的描述数据块里去,比如这个数据页所属的表空间之类的信息,最后把那个描述数据块从free 链表里去除就好了。

5.检查数据页有没有被缓存

在加载之前怎么知道这个数据已经被缓存过呢。
其实数据库还会有一个哈希表数据结构,他会用表空间号+数据页号,作为一个key,然后缓存页的地址作为value。

6. flush 链表

一旦更新了缓存页中的数据,那么缓存页里的数据和此案上的数据页里的数据,就不一致了,就是脏数据。该数据页就会变为脏页,但是不是所有的缓存页都刷回磁盘,因为有的缓存页可能是因为查询的时候被读取到Buffer Pool 里去的,可能根本没修改过。
引入flush 链表。这个flush 链表本质也是通过缓存页的描述数据块,组成一个双向链表。凡是被修改过的缓存页,都会把它的描述数据块加入到flush 链表中去,flush 的意思就是这些都是脏页。

7. LRU 链表

随着不停的把磁盘上的数据页加载到空闲的缓存页里去,free链表中的空闲缓存页会越来越少,迟早有那么一瞬间,会发现free 链表中已经没有空闲缓存页了。
此时你需要淘汰一些缓存页,即把一些缓存页里被修改过的数据,给他刷到磁盘上的数据页里去。那么应该把哪些缓存页的数据给刷入到磁盘中去呢,肯定那些很少被访问到的数据页。
那怎么知道哪些缓存页经常被访问,这时候就需要引入LRU 链表。它会将最近访问或者修改过的缓存页放在LRU链表的头部。如果此时缓存页不够用,只需要把LRU链表尾部的那个缓存页刷入到磁盘中,然后把你需要的磁盘数据页加载到腾出来的空闲缓存页中就可以了。