MySQL 缓冲池

为了加快数据库的整体性能,InnoDB 使用了 “缓冲池 Buffer Pool” 技术。

一、什么是缓冲池?

InnoDB 存储引擎将数据存储于磁盘中,为了弥补 CPU 速度和磁盘速度之间的鸿沟,InnoDB 使用了缓冲池技术。

简单来说,缓冲池就是一块内存区域,以 “缓冲” 的方式弥补磁盘速度较慢对数据库性能的影响。

二、缓冲池的工作方式

  • 加速读:

    执行读取操作时,判断缓冲池中是否有所需的页,

    • 如果有,直接读取缓冲池中的页
    • 如果没有,从磁盘中读取并放置到缓冲池中
  • 加速写:

    执行写入操作时,需要同时修改聚簇索引和多个二级索引,对于每个索引,需要:

    判断缓冲池中是否有对应的页,

    • 如果有,修改缓冲池中的页,异步刷新到磁盘

    • 如果没有,

      判断是否可以使用 change buffer,

      • 如果可以,将变更记录在 change buffer 中,待合适的时候将变更同步到数据页

      • 如果不可以,读取数据页至缓冲池,修改缓冲池中的页,异步刷新到磁盘

三、加速读

1. 初始化过程

  • 首先向操作系统申请内存空间
  • 将内存空间划分为若干对控制块和缓存页

2. 控制块

为了更好地管理缓冲池中的缓存页,每一个缓存页都会对应生成一个控制块,控制块中包含着与对应页有关的控制信息(表空间编号、页号、缓存页地址、链表节点信息、锁信息、LSN 信息等)。

3. free 链表

页从磁盘中读取后需要找到缓冲池中的存放位置,此时我们希望找到一个空闲的缓存页。

为了方便地获取空闲的缓存页,InnoDB 会将空闲的缓存页对应的控制块串联形成一个双向链表,这个链表被称为 free 链表。

因此,每当从磁盘中加载一个页,就从 free 链表中找到一个空闲的缓存页,向缓存页填入数据、向控制块填入控制信息、将控制块从 free 链表中移除。

4. hash 避免重复缓存

为了页的重复读取和缓存,以 表空间名 + 页号 为 key,以缓存页为 value,维护一个哈希表。当需要访问某个页的数据时,先从哈希表中尝试获取缓存页,当没有时,才到磁盘中加载。

5. LRU 链表

(1) 缓存页的释放问题

考虑这样一个问题,当缓冲区已经没有多余的空闲缓存页,但此时仍希望读入新的页,应该怎么做?

此时便需要释放部分缓存页,以腾出空间给新的缓存页使用,被释放的缓存页如何选取便是我们需要考虑的问题。

(2) 简单的 LRU 链表

具体请看:

数据结构 LRU 链表

(3) LRU 链表的优化

在 InnoDB 中存在两个会影响 LRU 正常运行的东西:

  • 预读:InnoDB 会自动推测接下来可能读取的页面,将它们预先加载到缓冲区中;这会导致缓冲区加载了不一定被使用的页,影响 LRU 链表的排序
  • 全表扫描:如果进行全表扫描,则意味着需要访问该表所出的所有页,这可能造成大量页面的访问;这会导致缓冲区载入很多只会被访问一次的页,LRU 链表被 “洗牌”

针对这两个问题,InnoDB 给出了以下解决方案:

  • 将 LRU 链表分为热、冷两截,分别表示使用频率高和低的缓存页

    1
    头节点 热1 热2 热3 热4 冷1 冷2 冷3
  • 当页面初次加载到缓冲区时,该缓冲页对应的控制块不会直接被放置到 LRU 链表的头部,而是会被放置到 “冷区” 的头部

  • 对于处在 “冷区” 中的缓存页,会在对应的控制块中记录最近访问时间,当且仅当访问间隔小于规定的最小访问间隔时,该存储页才可以被移动至 “热区”

四、加速写 - 直接写数据页

1. 脏页

如果修改了缓冲区中的某个缓存页中的数据,则它与磁盘上的页将变得不一致,这样的缓存页被称为 “脏页”。

2. flush 链表

对于 “脏页” 的处理,有一种最简单的方式,就是每发生一次修改就立即同步到磁盘上。这样虽然能够尽可能保证数据的一致性,但频繁的磁盘 IO 将会严重影响性能。

因此,我们会先将 “脏页” 记录起来,待到 “某个时候” 再将它们同步到磁盘上。

InnoDB 会将所有 “脏页” 对应的控制块串联形成一个双向链表,这个链表被称为 flush 链表。

3. 脏页刷新至磁盘

正常方式下,脏页刷新到磁盘有以下两种方式:

  • 单独线程、异步;定时;逆序扫描 LRU 链表中的 “冷区”,将一部分页面刷新至磁盘
  • 单独线程、异步;定时;顺序扫描 flush 链表,将一部分页面刷新至磁盘

当系统繁忙时,可能会出现 “希望读入新页,但缓冲区没有空间,LRU 链表尾部也没有可以直接释放的未修改页面” 的情况,此时可能会进行页的 同步 刷新。

五、加速写 - change buffer

1. 什么是 change buffer?

change buffer 是一个变更记录的缓冲区,通过它,可以在不读取数据页的情况下 “完成对数据的修改”。

在 MySQL 5.5 之前,只有插入缓存(insert buffer);

在 MySQL 5.5 之后,插入缓存被扩展,现在它叫做修改缓存(change buffer),可以优化 INSERT、DELETE、UPDATE。

2. 工作方式

  • 记录变更:将变更记录在 change buffer 之中

  • 应用变更(merge):将数据页读入缓冲池中,执行 change buffer 中的相关操作

    merge 触发的时机有:

    • 访问数据页
    • 后台线程定期 merge
    • 数据库正常关闭

3. 好处

减少磁盘 IO 和内存占用。

4. 使用条件

对于唯一索引,所有更新操作都需要判断该操作是否违反唯一性约束,这个判断需要将数据页加载进缓冲池中,因此没有再使用 change buffer 的必要。

因此,change buffer 不能用于聚簇索引、唯一索引,change buffer 能够用于普通索引。

5. 使用场景

对于写多读少的场景,使用 change buffer 可以减少磁盘 IO 和内存占用,使用效果最好。

反之,假设总是写入后立马查询,在这种情况下,在 change buffer 中记录操作后又需要立刻进行 merge,磁盘 IO 和内存占用并不会减少,反而增加了 change buffer 的维护代价。

参考

  • MySQL 技术内幕
  • MySQL 实战 45 讲
  • MySQL 是怎样运行的:从根儿上理解 MySQL