一、Buffer Pool的概述
为了缓存磁盘中的页,MySQL服务器启动时就向操作系统申请了一片连续的内存空间,他们给这片内存起名为——Buffer Pool(缓冲池)。
默认Buffer Pool只有128M,可以在启动服务器的时候配置innodb_buffer_pool_size(单位为字节)启动项来设置自定义缓冲池大小。
Buffer Pool对应的一片连续的内存被划分为若干个页面,默认也是16KB。该页面称为缓冲页。
为了更好的管理Buffer Pool中的这些缓冲页,InnoDB为每个缓冲页都创建了控制块。它与缓冲页是一一对应的。
控制块存放到Buffer Pool的前面,缓冲页存放到Buffer Pool的后面,如下所示:

二、free链表
Buffer Pool的初始化过程中,是先向操作系统申请连续的内存空间,然后把它划分成若干个【控制块&缓冲页】 对儿。
当插入数据的时候,为了能够知道哪些缓冲页是空闲可分配的,由此产生了free链表。
free链表是把所有空闲的缓冲页对应的控制块作为一个节点放到一个链表中,这个链表便称之为free链表。
free链表如下所示:

【注释】其中基节点是一块单独申请的内存空间(约占40字节)。并不在Buffer Pool的那一大片连续内存空间里。
磁盘加载页的流程
首先:从free链表中取出一个空闲的控制块(对应缓冲页)。
其次:把该缓冲页对应的控制块的信息填上(例如:页所在的表空间、页号之类的信息)。
最后:把该缓冲页对应的free链表节点(即:控制块)从链表中移除。表示该缓冲页已经被使用了。
三、flush链表
如果我们修改了Buffer Pool中某个缓冲页的数据,那么它就与磁盘上的页不一致了,这样的缓冲页也被称之为脏页(dirty page)。为了性能问题,我们每次修改缓冲页后,并不着急立刻把修改刷新到磁盘上,而是在未来的某个时间点进行刷新操作。
那么,如果有了修改发生,不是立刻刷新,那之后再刷新的时,我们怎么知道Buffer Pool中哪些页是脏页,哪些页从来没有被修改过呢?
答:创建一个存储脏页的链表,凡是被修改过的缓冲页对应的控制块都会作为节点加入到这个链表中。该链表也被称为flush链表。
flush链表的结构与free链表差不多。
flush链表如下所示:

那么,会存在一个控制块既是free链表的节点,也是flush链表的节点吗?
答:不会的。因为如果一个缓冲页是空闲的,那它肯定不可能是脏页。反之亦然。
后台有专门的线程负责每隔一段时间就把脏页刷新到磁盘。这样就不影响用户线程处理正常的请求了。
刷新方式有如下两种:
1> 从flush链表中刷新一部分页面到磁盘
后台线程会根据当时系统的繁忙程度确定刷新速率,定时从flush链表中刷新一部分页面到磁盘。——即:BUF_FLUSH_LIST
有时后台线程刷新脏页的进度比较慢,导致用户准备加载一个磁盘页到Buffer Pool中时没有可用的缓冲页。此时,就会尝试查看LRU链表尾部,看是否存在可以直接释放掉的未修改缓冲页。如果没有,则不得不将LRU链表尾部的一个脏页同步刷新到磁盘(与磁盘交互是很慢的,这会降低处理用户请求的速度)。——即:BUF_FLUSH_SINGLE_PAGE
2> 从LRU链表的冷数据刷新一部分页面到磁盘,即:BUF_FLUSH_LRU
后台线程会定时从LRU链表的尾部开始扫描一些页面,扫描的页面数量可以通过系统变量innodb_lru_scan_depth来指定,如果在LRU链表中发现脏页,则把它们刷新到磁盘。
控制块里会存储该缓冲页是否被修改的信息,所以在扫描LRU链表时,可以很轻松地获取到某个缓冲页是否是脏页的信息。
四、LRU链表
LRU = Least Recently Used
由于缓冲区空间有限,如果满了,则需要把旧的移除掉,新的加进来。为了把使用频繁的数据保留在缓存中,把使用频率低的数据移除。
用来记录缓冲页的被使用热度。
Buffer Pool的缓冲命中率(我们当然是期望命中率越高越好)
假设我们一共访问了n次页,那么被访问的页已经在Buffer Pool中的次数除以n,那么就是Buffer Pool的缓冲命中率。
提高命中率的方法——简单的LRU链表
【处理逻辑】
1> 创建LRU链表。
2> 当要访问某个页时,如果不在Buffer Pool,则把该页从磁盘加载到缓冲池的缓冲页时,就把该缓冲页对应的控制块作为节点塞到LRU链表冷数据区的头部。
3> 如果在Buffer Pool中,则直接把该页对应的控制块移动到LRU链表热数据区的头部。
LRU链表如下所示:
