一、缓存的重要性
所以 InnoDB 存储引擎在处理客户端的请求时,当需要访问某个页的数据时,就会把完整的页的数据全部加载到内存中,也就是说即使我们只需要访问一个页的一条记录,那也需要先把整个页的数据加载到内存中。将整个页加载到内存中后就可以进行读写访问了,在进行完读写访问之后并不着急把该页对应的内存空间释放掉,而是将其 缓存 起来,这样将来有请求再次访问该页面时,就可以省去磁盘 IO 的开销了。
二、InnoDB的Buffer Pool
-
什么是Buffer Pool?
设计 InnoDB 的大叔为了缓存磁盘中的页,在 MySQL 服务器启动的时候就向操作系统申请了一片连续的内存,他们给这片内存起了个名,叫做 Buffer Pool -
Buffer Pool内部组成
控制块和缓存页是一一对应的,它们都被存放到 Buffer Pool 中,其中控制块被存放到 Buffer Pool
的前边,缓存页被存放到 Buffer Pool 后边 -
free链表的管理
free链表:我们最好在某个地方记录一下Buffer Pool中哪些缓存页是可用的,这个时候缓存页对应的 控制块 就派上大用场了,我们可以把所有空闲的缓存页对应的控制块作为一个节点放到一个链表中,这个链表也可以被称作 free链表 -
缓存页的哈希处理
引出:我们怎么知道该页在不在 Buffer Pool 中呢?难不成需要依次遍历 Buffer Pool 中各个缓存页么?
实现:我们可以用 表空间号 + 页号 作为 key , 缓存页 作为 value 创建一个哈希表,在需要访问某个页的数据时,先从哈希表中根据 表空间号 + 页号 看看有没有对应的缓存页,如果有,直接使用该缓存页就好,如果没有,那就从 free链表 中选一个空闲的缓存页,然后把磁盘中对应的页加载到该缓存页的位置。 -
flush链表的管理
脏页:如果我们修改了 Buffer Pool 中某个缓存页的数据,那它就和磁盘上的页不一致了,这样的缓存页也被称为 脏页
flush链表:创建一个存储脏页的链表,凡是修改过的缓存页对应的控制块都会作为一个节点加入到一个链表中,因为这个链表节点对应的缓存页都是需要被刷新到磁盘上的,所以也叫 flush链表 。 -
LRU链表的管理
LRU链表引出:Buffer Pool 对应的内存大小毕竟是有限的,如果需要缓存的页占用的内存大小超过了 Buffer Pool 大小,也就是 free链表 中已经没有多余的空闲缓存页的时候岂不是很尴尬,需要把某些旧的缓存页从 Buffer Pool 中移除,然后再把新的页放进来喽~ 那么问题来了,移除哪些缓存页呢?
(1)简单的LRU链表
只要我们使用到某个缓存页,就把该缓存页调整到 LRU链表 的头部,这样 LRU链表 尾部就是最近最少使用的缓存页喽~ 所以当 Buffer Pool 中的空闲缓存页使用完时,到 LRU链表 的尾部找些缓存页淘汰就OK啦,
(2)划分区域的LRU链表
简单的LRU链表缺点:1. 加载到 Buffer Pool 中的页不一定被用到。
2. 如果非常多的使用频率偏低的页被同时加载到 Buffer Pool 时,可能会把那些使用频率非常高的页从Buffer Pool 中淘汰掉。
因为有这两种情况的存在,所以设计 InnoDB 的大叔把这个 LRU链表 按照一定比例分成两截,分别是:
1. 一部分存储使用频率非常高的缓存页,所以这一部分链表也叫做 热数据 ,或者称 young区域 。
2. 另一部分存储使用频率不是很高的缓存页,所以这一部分链表也叫做 冷数据 ,或者称 old区域 。 -
刷新脏页到磁盘
后台有专门的线程每隔一段时间负责把脏页刷新到磁盘,这样可以不影响用户线程处理正常的请求。主要有两种
刷新路径:
1. 从 LRU链表 的冷数据中刷新一部分页面到磁盘。
2. 从 flush链表 中刷新一部分页面到磁盘。 -
多个Buffer Pool实例