下饭,深入理解Buffer Pool原理

·  阅读 602

引言

在上一篇文章中,我们主要讲解了InnoDB存储引擎的架构,并提到了一个关键点:不管是查询还是更新操作,都是借助Buffer Pool来完成的。 所以,Buffer Pool的重要性不言而喻,我们今天就来更加进一步剖析Buffer Pool这个组件。

Buffer Pool大小

Buffer Pool本质上是一个内存组件,所以它是有大小限制的。默认情况下它的大小是128MB,在实际生产环境中,我们可以根据实际情况来调整Buffer Pool的大小,你可以通过show variables like 'innodb_buffer_pool_size' 命令来查看当前数据库的Buffer Pool的大小,单位是字节。

p1

Buffer Pool结构

现在我们知道了Buffer Pool的大小,那么Buffer Pool是以什么样的方式来存储数据的呢?

缓存页

对操作系统内存管理了解的同学都知道,操作系统中的非连续分配内存管理主要有三种方式:a) 页式管理、b)段式管理、c)段页式管理。其中页式管理就是:将逻辑地址空间划分为固定大小的页,同时物理内存也划分为同样大小的页框,程序加载的时候,可以将任意一页放入任意一个页框,页框可以不连续,从而达到非连续分配。

同样的,Buffer Pool既然也是内存组件,它采取的也是类似的方式。在Buffer Pool中,以页的方式存储数据,这些页称为:缓存页;与之相对应的是磁盘文件上的数据页。操作系统中,默认一页大小为4KB,而Buffer Pool中的缓存页大小为16KB。 在Buffer Pool中,每个缓存页还有与之一一对应的描述信息:缓存页的表空间、页编号等等,这些描述信息放在Buffer Pool的最前面,接着才是对应的缓存页,大概如下图所示

p2

free链

讲完了缓存页的结构,现在假设我们现在要查询一条数据,按照我们在前一篇文章中所说,InnoDB存储引擎会先在Buffer Pool中查找有没有这条数据,如果有直接就返回了,如果没有就需要去磁盘加载。结合上面所讲,应该是加载一页页的数据页到Buffer Pool中。那么此时问题就出现了:

  1. 怎么判断一个Buffer Pool中是否缓存了需要的数据?
  2. 哪些缓存页是空的可以用来缓存数据页呢?

现在,我们就来看看InnoDB是怎么做的。

首先问题1:想要高效快速的确定是否存在某个元素,第一想到的就是利用哈希,确实InnoDB也是这么做的,它有一个哈希表,其中哈希表的key是"表空间号 + 数据页号",value是"缓存页地址"。所以每次在Buffer Pool中缓存一个数据页时,就会在哈希表中增加一条记录,这样就可以快速的判断你需要的数据是否已经缓存在Buffer Pool中了。

问题2:free链,free链是一个双向链表(每个节点都有Pre和next指针),通过缓存页的描述信息将所有空闲的缓存页连接起来,并有一个基础节点,其中记录当前有多少空闲页,并且指向链表头和链表尾。

此时加载的过程就是:先看看基础节点中所记录的当前空闲缓存页数量,如果满足要求,就从free链里获取一个缓存页描述信息,通过描述信息就能找到一一对应的空闲缓存页,接着把磁盘数据页信息加载到空闲缓存页中,接着修改缓存页描述块的信息,将缓存页从free链中移除。

flush链

在上一篇文章中,我们在InnoDB存储引擎执行流程中有提到过,回台I/O线程会把Buffer Pool中的脏数据刷回磁盘。这里刷的其实就是脏缓存页,至于为什么是脏缓存页在上一篇中已经解释过了。同样这里会有一个问题,哪些缓存页是脏缓存页?

答案就是flush链,类似于free链,flush链也是一个双向链表,同样是利用了每个缓存页的描述信息来实现,同样它也有一个基础节点,来记录当前脏缓存页的数量。

I/O线程通过flush链就能知道哪些数据页是脏缓存页,把脏缓存页刷回磁盘,并从flush链中清除。

通过上面的描述,buffer pool的主要结构如下图所示:

p3

缓存淘汰机制

现在假设一种情况,free链中记录的空闲页数量为0,现在要执行一次查询,然而通过哈希表查找发现,数据并未存放在Buffer Pool中,那么就需要去磁盘将相应的数据页加载到Buffer Pool中,可是通过free链可以知道Buffer Pool中并没有空闲的缓存页。

首先想到的就是把一些脏缓存页刷回磁盘,那么不就有空闲的了么?问题是把哪些脏的缓存页刷回磁盘?这就涉及到缓存页的淘汰机制了,在操作系统中,类似的场景发生在发生缺页中断且物理内存已经被完全使用时,操作系统的页面置换算法一般有:先进先出(FIFO),最近最少使用(LRU),最近使用次数最少(LFU)等等。在InnoDB中,使用LRU作为缓存页淘汰策略。

LRU工作原理

通常LRU使用的数据结构是双向链表,为了加快查询速度,还会使用hash表,当缓存页被访问(查询、修改)就会把该缓存页放在链表头,所以链表尾一定是最近都没有访问过的缓存页。

为了提高缓存的命中率,InnoDB就认为,将链表尾部的缓存页刷回磁盘,对Buffer Pool的缓存命中率影响最低,所以他会把链尾的缓存页刷回磁盘,腾出空间来加载磁盘的数据页,同时将这些数据页放到链表头。

总结

本文主要讲解了InnoDB存储引擎中重要的内存组件:Buffer Pool。

首先讲解了Buffer Pool的默认大小及数据存储的格式,然后讲解通过free双向链表,InnoDB可以知道Buffer Pool中的空闲缓存页,通过哈希表可以知道需要的数据是否已经存在于Buffer Pool中,接着讲了通过flush双向链表,InnoDB可以知道哪些缓存页是脏缓存页,最后讲解了内存缓存淘汰策略LRU的工作原理。

分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改