MySQL机制介绍之Change Buffer

MySQL的偷懒行为之Change Buffer

在使用MySQL的时候,只要MySQL服务给你返回成功信息后,得到你需要的结果,你并不关注MySQL的行为;但是其实在MySQL内部,其实有很多的特性和机制来延迟真正的持久化等耗时耗资源行为。下面我将为你介绍其中的一个特性:change buffer

简介(What)

The change buffer is a special data structure that caches changes to secondary index pages when those pages are not in the buffer pool. The buffered changes, which may result from INSERT, UPDATE, or DELETE operations (DML), are merged later when the pages are loaded into the buffer pool by other read operations.

当需要改变二级索引的数据,并且该数据页不在内存缓冲池中;这个时候会直接向客户端返回修改成功的信息。MySQL并不实时的去更新磁盘数据,而是在之后一定条件下触发更新操作。这就是change buffer在作祟。

change buffer在MySQL5.5版本之前,只支持insert操作,所以最初被称为insert buffer。在之后的版本中,支持了更多的修改操作,所以后面改称为change buffer。

为什么需要change buffer(Why)

首先,你要明白二级索引的数据页在磁盘上存储得很散乱,读取的时候需要消耗大量的资源去做寻址等操作。这里涉及到了操作系统知识,并不在本文的科普范围。

其次,一般情况下,二级索引的数据访问的并不是很频繁,作为冷数据的存在,即使读取到了内存当中,很快也会被挤出内存缓冲池。如果每次更新操作都读取进来,那也是一笔很大的开销,所以MySQL会将多次更新给合并到一块去更新。(这里涉及到了LRU的淘汰知识,后续会有专门的文章讲解,这里你只需要知道有这个东西就行了)

所以,change buffer的主要作用有以下两点:

  1. 减少了MySQL随机读取磁盘的次数
  2. 多次的随机写磁盘操作,合并成了一次的顺序写磁盘操作

这里再介绍一下change buffer的限制条件:非唯一的辅助索引

前面我们说过二级索引的数据在磁盘上分布的很散乱,但是聚簇索引就不会这样;我们都知道Innodb的数据都存储在聚簇索引的叶子节点,Innodb为了保证能够更快的读取数据,会将聚簇索引的数据页尽可能在物理上顺序放在一起

此外,在修改聚簇索引和唯一辅助索引上的数据时,为了保证数据的唯一性,总是会在进行修改操作前进行一次查询操作,这个时候已经将该数据页读取到内存缓存池中了。直接修改内存中数据页上的数据信息更快,完全没有必要使用change buffer。

change buffer干了什么(DO)

执行过程

下面我们来看change buffer的执行过程:

  1. 如果需要修改的数据页在内存中,则直接修改内存数据并返回
  2. 如果需要修改的数据页不在内存中,则向change buffer中添加一条记录(修改数据页的记录)
  3. 在redo log中添加一条记录(change buffer页的修改记录)
    V7JVC4.jpg

从change buffer的执行过程中,我们可以看到,如果需要修改的数据页page 2不在内存中时,Innodb并不会去磁盘中读取,而是在change buffer中记录了page 2中的数据变更。

系统会周期性的将change buffer的数据写入到系统表空间中。所以不需要担心change buffer的数据丢失问题。

什么时候进行合并操作

  • 二级索引页被读取到buffer pool中,在页面可用之前,会先进行合并缓存操作
  • change buffer bitmap页追踪到该辅助索引页无可用空间时
  • Master Thread定时任务
  • 在崩溃恢复时,索引页被读入到缓冲池时,会将存在系统表空间的change buffer应用到二级索引的叶子节点
  • 当–innodb-fast-shutdown=0时,数据库实例关闭时会强制将change buffer合并到数据页中并刷盘。

change buffer的存储

V7J9uq.md.jpg

change buffer在物理上是以一个普通的btree存储。根页是存储在系统表空间的固定页中(涉及innodb的数据存储结构,太高深了~还没到那个层次,就看个热闹)

上图是一条ibuf记录的存储格式,通过(space id, page no , counter)三列作为主键来唯一决定一条记录,其中counter是一个递增值,目的是为了维持不同操作的有序性。可以通过counter来保证merge时执行时的顺序和用户操作的顺序一致。

在插入时,会先将counter的值设置成0xFFFF;然后将定位到小于等于(space id, page no, 0xFFFF)的位置,再将counter为当前记录counter值加1。(这样做的原因是为了合并的时候能够更方便的找到同一数据页的缓存,并保证有序)

V7JSvn.md.jpg
因为ibuf缓存是针对具体的数据页的,因此在缓存操作的时候需要避免空页和满页的缓存操作。(满页:即将分裂的页,并不一定是所有空闲空间用完的页,一个数据页会有1/16的空隙)

  1. 针对空页情况,在准备插入IBUF_OP_DELETE类型的操作缓存时,会预估执行完此操作后该page中还存在多少记录,如果只剩下一条记录,则走正常流程,将数据页读取到内存中。
  2. 针对满页情况,则是通过ibuf_bitmap页记录,该页存在于每个ibd的固定位置;ibuf_bitmap中记录了每个数据页的缓存状态和空闲空间大小。
    在执行IBUF_OP_INSERT类型的操作前,会将该page的IBUF_BITMAP_BUFFERED设置为true。
    通过IBUF_BITMAP_FREE来记录该page的空闲空间;在插入前,会去找到该page的空闲空间大小,如果本次操作超出限制,则会触发一次异步ibuf merge。

merge过程

  1. 一个磁盘上的数据页被读取到内存缓冲池中
  2. 到ibuf_bitmap中判断该数据页是否有缓存行为
  3. 如果存在缓存,则从ibuf的btree树中读取该数据页的缓存操作;并应用到该内存数据页上
  4. 只有当应用缓存完成,才能提供给用户查询等操作行为
  5. 在一定情况下执行刷盘操作,持久化到磁盘

merge触发情况

  1. 二级索引页被读入到内存中
  2. 插入ibuf操作时,遇到满页,会造成页分裂操作时,触发一次异步的merge
  3. 当前ibuf tree size>max_size + 10时,会执行一次同步的merge
  4. 本次插入ibuf操作可能会导致ibuf tree索引分裂时,根据ibuf size的大小情况会触发不同情况的merge
  5. master定时任务会合并相应数量的buffer
  6. 对某个表执行flush table时,会强制对该表的page进行merge
  7. 数据库关闭时,根据innodb_fast_shutdown来决定是否进行merge

相关参数

  1. innodb_change_buffering
    innodb_change_buffering参数可以控制什么操作会使用change buffer。默认是all

    |||
    | — | — |
    | 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 physical deletion operations that happen in the background. |

  2. innodb_change_buffer_max_size
    innodb_change_buffer_max_size参数控制change buffer占buffer pool的百分比。默认25%,最大50%

  3. 查看change buffer的使用情况

    1
    2
    3
    4
    5
    6
    7
    mysql>SHOW ENGINE INNODB STATUS \G
    INSERT BUFFER AND ADAPTIVE HASH INDEX
    Ibuf: size 1, free list len 56, seg size 58, 517693 merges
    merged operations:
    insert 560451, delete mark 0, delete 0
    discarded operations:
    insert 0, delete mark 0, delete 0

Ibuf表示当前缓冲区中change buffer的页使用情况
size表示已使用的页数,free list len表示空闲的页,seg size表示总共的页数(等于size + free list len + 1),merges合并数
merged operations表示change buffer中每个操作的次数。
insert表示Insert Buffer,delete mark表示Delete Buffer,delete表示Purge Buffer。
discarded operations表示当change buffer发生merge操作时,表已经被删除,此时不再需要将记录合并到辅助索引中去。

从上面的信息可以看出这个数据库实例的change buffer效果并不明显(通过insert/merges计算)。change buffer适用于存在大量修改二级索引页的DML活动中。

总结

change buffer将随机IO转换成顺序IO,避免了IO带来的性能损耗,减少了随机读磁盘的次数,提高了数据库的写性能。

参考资料

https://dev.mysql.com/doc/refman/5.7/en/innodb-change-buffer.html
https://dev.mysql.com/doc/refman/5.7/en/faqs-innodb-change-buffer.html
http://mysql.taobao.org/monthly/2015/07/01/
https://blog.csdn.net/bohu83/article/details/81837872