谈谈InnoDB核心组件--Buffer Pool

510 阅读6分钟

前言

我们了解到buffer pool 是InnoDB独有的一个内存结构,之前初步了解到数据的增删改都是在这块内存里面执行。因为我们不可能直接在磁盘中对数据进行增删改,如果对磁盘的随机读写,速度会非常的慢,就更别谈每秒处理上千的请求了。之前也描述过虽然是在buffer pool是在内存中,但MySQL有非常严谨的兜底方法,就算宕机了,也可以配合 redo log 来进行数据的更新和刷盘。那buffer pool究竟长什么样子?

1. buffer pool 占用多少内存,多少合适?

buffer pool是一个内存数据结构,在MySQL内部肯定是占有一定的内存。

buffer pool的默认大小是128M,这个可以根据实际环境来进行分配。MySQL官网给出的建议是大小占用实际环境的80%,比如MySQL服务器是一个8核16G的机器,则可以分配12G内存。

innodb_buffer_pool_size = 12,884,901,888

图 1

2. buffer pool 存储的是什么样的数据?

大家都清楚数据库的核心数据模型是表+字段+行的概念,也就是说数据库有很多个表,表里面有很多的字段,每个字段都有对应的值,一行一行的数据构成了表。那有没有可能buffer pool储存的是一行一行的数据?

答案不是的。实际上MySQL对数据抽象出了一个数据页的概念,他把很多行数据放在了一个数据页里,也即是说磁盘文件里有很多的数据页,每一个数据页里放了很多行数据。

图 2

所以实际上,更新一行数据,数据库找到这行数据所在的数据页,然后从磁盘中把数据页加载到buffer pool中。也就是说,buffer pool存放的是一个一个的数据页。

图 2-2

3. 磁盘中的数据页与buffer pool中的缓存页如何对应起来?

默认情况下,一个数据页的大小为16kb,,包含了16kb的内容,而buffer pool中缓存页跟数据页是一一对应的,即是说buffer pool中缓存页的大小也为16kb。

图 3

而每个缓存页在buffer pool中都有对应的描述信息。描述信息也是一个数据块,相当于缓存页大小的5%,里面描述了该数据页所属的表空间、数据页的编号、这个缓存页在buffer pool中的地址等信息。每个描述信息放在buffer pool最前面,各个缓存页放在后面。

图 3-2

4. 初始化buffer pool

数据库在启动阶段是如何初始化buffer pool的呢?

当数据库启动的时候便会向操作系统申请一块内存给buffer pool,内存区域申请完毕后,就会按照默认的缓存页大小16kb和描述信息块的800左右的字节在buffer pool中划分出来一个一个的缓存页和他们所对应的描述数据块。

只不过这时的缓存页都是空的,等数据库完全运行起来,我们执行增删查改的时候,才会把对应的数据页加载到缓存页中。

5. 如何区分空闲缓存页--free链表

在不停的增删改查下,MySQL会不停的把数据页从磁盘中加载到缓存中里。随着16kb的数据页不停的加载,那是如何判断哪些缓存页是空闲的?

MySQL为buffer pool设计了free链表。在buffer pool中,每一个空闲的缓存页对应的描述数据块通过头节点和尾节点连接在一起,形成一个free链表。

每当数据页加载到buffer pool中,对应描述数据块就会记录相关的信息,找到相应的缓存页缓存数据页,然后从free链表中移除此描述数据块。

即如果缓存页是空闲的,他的描述数据块必定存在这个双向链表中。

图 5

除此之外,还会有一个基础节点,引用链表的头节点和尾节点,记录free链表的节点数,即空闲缓存页的个数。

free链表是由buffer pool的空闲描述数据块组成,并不会占用额外的内存空间,如果缓存页加载了数据,只会把节点从链表中移除。

6. 把磁盘的数据页加载到buffer pool的缓存页

有了free链表,这一步就很简单了。

首先从free链表中获取一个描述数据块,就可以获取到对应的空闲缓存页,写入描述信息,把磁盘上的数据页读取到缓存页,最后移除对应的描述数据块。

图 6

关于数据块如何从free链表中移除,可以参考以下伪代码: 假设节点02,上一个节点是01,下一个节点是03

DescriprionDataBlock() {
    block_id = 02;
    free_pre = 01;
    free_next = 03;
}

当03节点从free链表中移除,则把free_next 设置为null就可以了,03在free链表中就是去了引用关系。

DescriprionDataBlock() {
    block_id = 02;
    free_pre = 01;
    free_next = null;
}

7. 如何判断数据页有没有被缓存?

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

如果数据页已经存在缓存里,那么就会直接使用。

所以MySQL还会有一个哈希表数据结构,用表空间号+数据页编号作为kye,缓存页地址作为value。

当需要数据页的时,就会用表空间号+数据页编号作为key去哈希表查,如果没有就读取数据页,如果有就说明数据页已经被缓存了。

也就是说,每将数据页读取到缓存页,都会在哈希表写入一个key-value。

图 7

8. 脏页--flush链表

在buffer pool的缓存页中,有查询出来的还没有更新的,有已经更新过数据的。更新过数据的缓存页肯定和磁盘上的不一致,这些缓存页便是脏数据,脏页。

这些脏页最终都是被随机刷入磁盘,但如何区分哪些缓存页是脏页?

数据库引入了跟free链表相似的flush链表,本质上flush链表也是有缓存页的描述数据块的两个指针组成的一个链表,只是这里是被修改过的缓存页的描述数据块。

但凡修改过的缓存页,都会把它相应的描述数据块加入到flush链表中,等待刷盘。

图 8