介绍

在 MySQL 5.5 之前,叫插入缓冲(Insert Buffer),只针对 Insert 做了优化,现在对 Delete 和 Update 也有效,叫做写缓冲(Change Buffer)。

它是一种应用在非唯一普通索引页(non-unique secondary index page)不在缓冲池中,对页进行了写操作,并不会立刻将磁盘页加载到缓冲池,而仅仅记录缓冲变更(Buffer Changes),等未来数据被读取时,再将数据合并(Merge)恢复到缓冲池中的技术。Change Buffer 的目的是降低写操作的磁盘 IO,提升数据库性能。

Change Buffer 是一个持久化的对象,存放在 ibdata1 文件中,同时也会写 redo log。

官方文档:https://dev.mysql.com/doc/refman/8.0/en/innodb-change-buffer.html

原理

执行流程

  1. CREATE TABLE user (
  2. id INT AUTO_INCREMENT, -- id 列是自增的
  3. name VARCHAR(30), -- name 列是varchar
  4. PRIMARY KEY(id) -- id 是主键
  5. key(name) -- name 是非唯一二级索引
  6. );

比如 user 表新增一条数据,对主键索引和非唯一二级索引会执行不同的存储步骤:

  • 对于主键(id 列),每次插入都要立即插入对应的聚集索引页中(在内存中就直接插入,不在内存就先读取到内存)。
  • 对于非唯一二级索引(secondary index)(name 列)
    • 在没有 Change Buffer 之前,每次插入一条记录,就要读取一次页(读取内存,或者从磁盘读到内存),然后将记录插入到页中。
    • 在有 Change Buffer 后,当插入一条记录时,先判断记录对应要插入的二级索引(secondary index)页是否在 Buffer Pool 中:
      • 如果该二级索引(secondary index)页已经在 Buffer Pool 中,则直接插入。
      • 反之,先将其 Cache 起来,放到 Change Buffer 中,等到该二级索引(secondary index)页被读到时,将 Change Buffer 中该页对应的记录合并(Merge)进去,从而减少 I/O 操作次数。

Change Buffer 就是用来减少 I/O 操作次数,提升二级索引插入的性能 。

Change Buffer 其实就是使用空间换时间,利用批量插入的方式减少 I/O 操作次数,因为 InnoDB 是索引组织表的存储方式,主键索引的叶子节点存放了完整的记录,二级索引可以先不着急插入,只需要先插入主键即可。

通过如下案例形象的描述 Change Buffer 带来的好处:

情况一:假如要修改页号为 4 的索引页,而这个页正好在缓冲池内。
图片.png
如上图序号1-2:

  1. 直接修改缓冲池中的页,一次内存操作。
  2. 写入 redo log,一次磁盘顺序写操作。

这种情况的效率是最高的。

情况二:假如要修改页号为 40 的索引页,而这个页正好不在缓冲池内。
图片.png
此时麻烦一点,如上图需要1-3:

  1. 先把需要为 40 的索引页,从磁盘加载到缓冲池,一次磁盘随机读操作。
  2. 修改缓冲池中的页,一次内存操作。
  3. 写入 redo log,一次磁盘顺序写操作。

没有命中缓冲池的时候,至少产生一次磁盘 I/O 操作。

InnoDB 在引入 Change Buffer 后,上文“情况二”流程会有什么变化?

假如要修改页号为 40 的索引页,而这个页正好不在缓冲池内。
图片.png
引入 Change Buffer 优化后,流程优化为:

  1. 在写缓冲中记录这个操作,一次内存操作。
  2. 写入 redo log,一次磁盘顺序写操作。

其性能与这个索引页在缓冲池中相近。

稍后的一个时间,有请求查询索引页 40 的数据。
图片.png
此时的流程如序号 1-3:

  1. 载入索引页,缓冲池未命中,这次磁盘IO不可避免。
  2. 从写缓冲读取相关信息。
  3. 恢复索引页,放到缓冲池 LRU 里。

哪些场景会触发 Merge 操作呢?

在执行 Merge 操作之前,Change Buffer 数据是存在内存中,为了防止数据库意外宕机导致数据丢失,系统会周期性将 Change Buffer 数据写入到共享表空间中。

  1. 辅助索引页被读取到 Buffer Pool 中

    1. 例如这在执行正常的 select 查询操作,索引页被调入内存,该索引页对应在 Change Buffer 中的索引更改记录就会发生 Merge 操作。
  2. ibuf bitmap 页追踪到该辅助索引页已无可用空间时

  3. Master Thread 工作

    1. 在 Master Thread 线程中每秒或每 10 秒会进行一次 Merge 的操作,不同之处在于每次进行 Merge 操作的页的数量不同。
  4. MySQL 正常关闭时

  5. redo log 写满时

    1. 几乎不会出现 redo log 写满,因为当 redo log 写满了,整个数据库将处于无法写入的不可用状态。

为什么写缓冲优化,仅适用于非唯一普通索引页呢?

如果索引设置了唯一(unique)属性,在进行修改操作时,InnoDB 必须进行唯一性检查。也就是说,索引页即使不在缓冲池中,磁盘上的页读取也无法避免,此时就应该直接把相应的页放入缓冲池再进行修改。

执行效果

通过如下命令可以查看 Change Buffer 的执行情况:

  1. mysql> show engine innodb status\G
  2. --------------省略其他输出-------------
  3. -------------------------------------
  4. INSERT BUFFER AND ADAPTIVE HASH INDEX
  5. -------------------------------------
  6. Ibuf: size 1, free list len 0, seg size 2, 0 merges -- 这里为0,可能是Buffer Pool足够大,
  7. -- 数据页都缓存在内存中了,就用不到Change Buffer
  8. merged operations:
  9. insert 0, delete mark 0, delete 0
  10. discarded operations:
  11. insert 0, delete mark 0, delete 0
  1. seg size:页的数量,例如当前页为 8K,则 seg_size * 8K 就是 Change Buffer 使用的内存大小。
  2. merges:合并了多少页。
  3. merged insert:插入了多少条记录。
    1. insert / merges:就是插入的效率(插入一条记录,就要读取一次页)。
  4. discarded operations:应该是很小的值,或者为 0,当记录写入到 Change Buffer 后,对应的表被删除了,则相应的 Buffer 中的记录就应该被丢弃。

使用 Change Buffer 的前提时,需要使用随机 IO ,这时才放入 Change Buffer 中,如果页已经在 Buffer Pool中,就不需要使用 Change Buffer 了。

举例说明 Change Buffer 的执行效果:

假设:merges = 10,insert = 1000,delete mark = 1000,delete = 1000

Merge 一次可以处理的记录数:(1000 + 1000 + 1000) / 10 = 300。

注意:如果 merges 的数据较大,说明 Change Buffer 调小了,也可能是非唯一索引建的多了。对表进行批量 IDU 的时候,可能会导致 Change Buffer 迅速增加。

参数

Change Buffer 有两个重要参数:

  1. mysql> show variables like "%change_buffer%";
  2. +-------------------------------+-------+
  3. | Variable_name | Value |
  4. +-------------------------------+-------+
  5. | innodb_change_buffer_max_size | 25 |
  6. | innodb_change_buffering | all |
  7. +-------------------------------+-------+
  8. 2 rows in set (0.00 sec)
  • innodb_change_buffer_max_size:配置 Change Buffer 的大小占整个缓冲池的比例,默认值是 25%,最大值是 50%。
  • innodb_change_buffering:配置哪些写操作启用 Change Buffer,可以设置成 all、none、inserts、deletes、changes、purges。 | Value
    | Description | | :—-: | :—-: | | all | The default value: buffer inserts, delete-marking operations, and purges. | | none | Do not buffer any operations. | | inserts | Buffer insert operations. | | deletes | Buffer delete-marking operations. | | changes | Buffer both inserts and delete-marking operations. | | purges | Buffer the physical deletion operations that happen in the background. |

性能

图片.png
左图使开启了 Change Buffer,而右图未开启,一开始都比较高是因为还没有全量的进行刷磁盘(脏页全部在 Buffer Pool 中,还没有满),当执行 Merge 操作的时候,Insert 的性能下降,对比右图,开启 Change Buffer 后,Rows inserted per second 的值在 5k 左右,SSD 场景下也建议开启 Change Buffer。

参考

写缓冲(change buffer),这次彻底懂了!!!
InnoDB关键特性之change buffer

作者:殷建卫 链接:https://www.yuque.com/yinjianwei/vyrvkf/zytfa0 来源:殷建卫 - 架构笔记 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。