Mysql的Buffer Pool

Mysql中的数据是存放在磁盘中的,假如每次查询都直接从磁盘中直接查询会很影响性能,所以在Mysql中设计了一套先把数据从磁盘中取出,然后放在内存中的方案,这样等下一次查询的时候直接从内存中读取数据进而提高了性能。Mysql在服务器的内存中会有一个专门的区域来处理这些缓存数据,这个区域就是Buffer Pool。

1、认识Buffer Pool

在Mysql启动的时候会申请一块连续的内存用来缓存数据,这块连续的物理内存就是Buffer Pool,Buffer Pool是用来进行数据缓存,设计的初衷是减少磁盘IO的次数来提高数据查询的性能。

Buffer Pool的默认的空间大小是128M(这里是指缓存页的总大小,控制块是单独的申请的6M空间),当前这Buffer Pool占用空间的大小是可以通过参数设置的,推荐设置成物理内存的60%,从磁盘加载数据到Buffer Pool中的流程如下所示:

2、Buffer Pool的结构

为了提高在高并发、多线程下的Mysql的响应速度,将Buffer Poo l 拆分成多个小的实例,如下图所示:

我们可以通过参数(innodb_buffer_pool_instance)可以控制Buffer Pool的实例个数。

每个小的实例下面又分成若干个chunk,如下图所示的结构图:

每个chunk下面又可以分成控制块和缓存页,如下图所示的结构:

每个instance下面有若干个chunk,每个chunk下又有若干个控制块和缓存页组成,整体的结构如下所示:

控制块存储的是缓存页的编号、缓存页所属的表空间、缓存页在Buffer Pool中的内存地址、链表信息等。每个chunk开始的时候都会拿出一块空间(也就是一页,大小为16KB)来存储控制块的信息,如下所示:

如果第一个页可以存储下所有的控制块信息,那么就不会在开辟新的页了;如果第一个页无法存储全部的缓存页信息,那么就会开辟第二个页存储缓存页的信息,如下所示:

当所有的缓存页信息都被控制块记录下来之后,那么就会由一个frame指针指向第一个缓存页,如下图所示:

有时候会出现一个比较尴尬的情况,当开辟了3个页来存储的所有缓存页信息的时候,block3中的所有空间并没有完全被使用完,那么就出现了内存碎片,这个碎片的空间也不会再被使用了,如下所示:

控制块占用的内存是非常的小的,大约占用chunk的5%大小的内存空间。控制块和缓存页是一一对应的关系如下所示:

3、Buffer Pool缓存和查询数据

在innobd中维护一套控制块的信息,也就是将控制块的信息维护在链表上,如下图所示:

Buffer Pool在初始化完成后所有的缓存页都是空闲的,那么所有的控制块都会放在free链表上,并且将count值设置成控制块的数量。假设现在缓存页2上从磁盘上加载数据了,此时就要将缓控制块2从free链表上移除并且count值减去1,如下图所示:

通过将磁盘的数据加载并且缓存在Buffer Pool的缓存页上的设计目的就是提高Mysql的查询速度,但是innodb如何知道数据在不在缓存页中呢?其实在Buffer Pool有一个专门的哈希表,这个表中存储表空间+页号为key,缓存页地址为value的哈希表,如下图所示的哈希表结构:

Mysql每次读取数据的时候会先从哈希表中读取,如果哈希表中存在数据,那么直接从Buffer Pool中的缓存页上获取数据;如果哈希表中无数据就会从磁盘中将数据缓存到Buffer Pool中,假设当前数据加载缓存页8中,其加载过程如下图所示:

4、buffer pool中数据修改

假设Buffer Pool现在对缓存页2中的数据做修改之后,如下图所示:

此时缓存2中发生数据修改后和磁盘中的数据不一致了,我们称之为脏页。那么脏页的数据如何同步到磁盘中呢?简单一点就是发生了脏页就立刻写入硬盘中,但是频繁的写入会影响到Mysql的性能,于是就出现了flush链表。

flush链表用来存储哪些页的数据要同步到硬盘上,同时还维护了一个计数器用来计数多少的页需要同步到硬盘上。如下如所示的flush链表结构:

当脏页的数量达到一定的程度之后,Mysql开始将脏页的数据使用异步的方式同步到磁盘上,如下所示:

至此我们了解了Buffer Pool中如何加载和缓存磁盘数据、如何在缓存页中读取数据,也知道了Buffer Pool中发生数据变更之后如何同步到磁盘。

0