InnoDB 的 Buffer Pool

对于使用InnoDB存储引擎来说,无论是存储用户数据的索引,还是各种系统数据,最终都是以文件的形式存放于磁盘中,InnoDB存储引擎 在处理客户端的请求时,如果需要访问某个页的数据,就会把完整的页中的全部数据加载在内存中,而且在读写访问之后,并不着急把该页的内存释放掉,而是将其缓存起来,这样将来有请求访问该页时,就可以省下磁盘IO的开销。

Buffer Pool

为了缓存磁盘中的页,MySQL 启动时就会向OS申请一大块连续的内存,这就是 Buffer Pool。该值也可以在配置文件中配置:

最小值为5MB,小于5MB会自动配置为5MB。

Buffer pool 对应的连续内存被划分为若干个页面,页面大小于InnoDB 表空间使用的页面大小一致,默认都是16KB,我们将其称为缓冲页。为了管理这些缓冲页,InnoDB 为每一个缓冲页都创建了一些控制信息,包括:表空间编号,页号,缓冲页在 Buffer Pool 中的地址,链表节点信息等。

这些缓冲页对应的控制信息占用的内存大小都是相同的,我们把每个页对应的控制信息占用的一块内存称为一个控制块。控制块和缓冲页都存放到 Buffer Pool 中,控制块存放到 Buffer Pool 前面,缓冲页存放到 Buffer Pool 的后面,所以整个 Buffer Pool 对应的内存空间看起来如图所示:

free 链表的管理

最初启动MySQL,完成 Buffer pool 初始化之后,现在并没有磁盘页被缓存到 Buffer Pool 中,那么当从磁盘读取到第一页到 Buffer poo 中时,该存放到Buffer Pool 中的哪一个缓冲叶呢?或者说怎么区分缓冲页哪些是被使用了,哪些是没有被使用呢?

MySQL 是这样做的,把所有空闲缓冲页的控制块作为一个链表节点,依次串起来,我们将这个链表称为 free 链表。

  • 缓冲页的hash处理
    当我们需要访问某个页中的数据时,就会把该页从磁盘加载到 Buffer Pool 中,但是如果该页已经在 Buffer Pool 中,那么直接使用就可以了。那么问题来了,我们怎么知道该页在不在 Buffer Pool 中呢?
    我们其实是用 表空间号 + 页号 来定位一个页的。也就相当于 key = 表空间号 + 页号,value=缓冲页的控制块。怎么通过一个key来快速找到一个value呢?当然是哈希表了。

    所以在需要访问某个页的数据时,先从哈希表中根据表空间号+页号看看是否有对应的缓冲页,如果有,直接使用该缓冲页就好,如果没有,就从free链表中选择一个空闲的缓冲页,然后把磁盘中对应的页加载到该缓冲页的位置。

flush 链表的管理

如果我们修改了 Buffer Pool 中的某个缓冲页的数据,它就与磁盘上的页不一致了,这样的缓冲页称为脏页。当然我们可以每当修改完某个缓冲页时,就立即将其刷新到磁盘对应的页上,但是频繁的往磁盘写数据会严重影响磁盘性能。所以,并不着急将修改刷新到磁盘上,而是在未来的某个时间节点上。

为了管理这些脏页,如同 free 链表类似的思路,建立一个存储脏页的链表,凡是被修改过的缓冲页对应的控制块都会作为一个节点加入到这个链表中,这个链表称为flush链表。

LRU 链表的管理

Buffer Pool 对应的内存大小总是有限的,当 free 链表已经没有多余空闲缓冲页时,就需要把旧的缓冲页从 Buffer Pool 中移除,然后再把新的页放进去,那么,就有个问题产生了,移除哪些缓冲页呢?

简单的LRU链表
MySQL 这里采用的时LRU算法(按照最近最少使用的原则去淘汰缓冲页),按照同样的方法再创建一个链表,当需要访问某个页时,按照下面的方法来处理:

  • 如果该页不在中,在把该页从磁盘加载到 Buffer Pool 中的缓冲页时,就把该缓冲页对应的控制块作为节点塞到LRU链表头部。
  • 如果该页已经被加载到 Buffer Pool 中,则直接把该页对应的缓冲控制块移动到LRU链表头部。

也就是说,只要我们使用到了某个缓冲页,就把该缓冲页调整到 LRU 链表头部,这样,LRU链表尾部就是最近最少使用的缓冲页了,所以,当 Buffer Pool 中的空闲链表使用完时,直接到LRU链表的尾部找些缓冲页淘汰掉就行了。

  • 划分区域的 LRU 链表
    上面的这个简单的 LRU 链表用了没多长时间就发现问题了。它不能对付下面2种情况:
    1. 情况一:InnoDB 提供了一个看起来比较贴心的服务 -- 预读。前边说过:只有当我们用到某个页时,才会将其从磁盘加载到 Buffer Pool 中,用不到则不加载。而预读就是:当InnoDB 执行当前请求时,可能会在后面读取到某些页面,就预先把这些页面加载到 Buffer Pool 中。按照触发方式不同,预读又分为:

      • 线性预读
      • 随机预读

      预读

    2. 全表扫描

上面2种问题,总结出来:

  • 加载到 Buffer Pool 中的页不一定用得到
  • 如果有非常多的使用频率偏低的页被同时加载到 Buffer Pool 中,则可能会把哪些使用频率非常高的页从 Buffer Pool 中淘汰掉

刷新脏页到磁盘

后台有专门的线程负责每隔一段时间就把脏页刷新到磁盘,这样就可以不用影响用户线程处理正常的请求。刷新的方式主要有2种:

  • 从LRU链表的冷数据种刷新到一部分页面到磁盘

  • 从flush链表的冷数据刷新一部分页面到磁盘

查看Buffer Pool 状态信息

MySQL 提供了 `` 语句来查看InnoDB存储引擎运行过程中的一些信息,其中就包括 Buffer Pool 的信息。

你可能感兴趣的:(InnoDB 的 Buffer Pool)