MySQL中的change buffer
参考:
https://blog.csdn.net/weixin_38304221/article/details/88635432
https://blog.csdn.net/nanjingitany/article/details/102856472
https://www.cnblogs.com/abclife/p/7625281.html
https://www.cnblogs.com/ging/p/13467833.html
MySQL中的change buffer
MySQL的一条语句,大致流程查看内存→读取磁盘数据页→返回数据。
当比如查找一个 a=5的记录的时候,并不是只查找出这一条数据,它所在的整个数据页都会查找出来(每个数据页16KB)。
下次查找a=6的记录的时候,发现该页已经在内存中了,直接返回,不需要磁盘IO。
但是当时增、删、改操作时,并不会每一次操作都进行一次磁盘IO,使用change buffer可以降低磁盘随机IO。
change buffer首先是可持久化的数据。
当更新某个数据页时,该页在内存中,那么直接更新。
如果该页不在内存中,那么先将更新操作记录在change buffer中,这时不需要从磁盘中读出数据页。
将操作记录到redo log中,防止机器意外关闭导致数据丢失。
这时已经可以返回给客户端更新成功了,因为即使机器意外重启,也可以通过redo log找回数据。
change buffer 使用的是 buffer pool里的内存,不能无限增大,可以通过参数 innodb_change_buffer_max_xize来动态设置,这个参数为50的时候,标识change buffer 的大小最多只能占用 buffer pool 的50%。
什么时候将change buffer中的数据更新到磁盘中?
当下一次查询命中这个数据页的时候,会先从磁盘中读取数据页到内存中,然后先执行change buffer的merge操作,保证数据逻辑的正确性。除了查询操作外,系统有后台线程会定期merge,数据库正常关闭(shutdown)的时候,也会进行merge操作。
change buffer 和 redo log 对于磁盘的随机IO影响。
redo log是减少 随机写磁盘IO 的消耗。每个操作先记录redo log,系统空闲时或redo log满时进行磁盘IO。
change buffer是减少 随机读磁盘IO 的消耗。更新时如果内存中不存在该数据页,也不需要马上进行磁盘IO,而是先记录在change buffer中,等待时机统一merge。
所以根据业务场景选择是否使用change buffer。
当写少读多的情况下:change buffer的利用率不高,因为可能刚一更新完可能就触发merge操作,change buffer没有起到减少随机IO,还多了一个维护change buffer的成本。
当写多读少的情况下:change buffer的提升效果较明显,可能很多条更新后,也没有一个查询线程来触发merge操作,可以大幅减少磁盘的随机IO。
唯一索引和 普通索引(在业务逻辑层保证数据唯一)的选择
命中唯一索引和普通索引时MySQL筛选数据的逻辑不同。
比如一个查询语句 select * from table where k=5;先通过B+树从树根开始,按层搜索到叶子节点,也就是数据页。
唯一索引 k:由于索引定义了唯一性,所以在数据页中找到第一个满足条件的记录后,直接返回。
普通索引 k:没有定义索引的唯一性,所以在查找到第一条满足k=5后,还会继续遍历下一条,直到查到的k != 5的时候,结束遍历。
那么两种索引的效率差距有多大呢?
其实是很小,几乎可忽略。普通索引相对于唯一索引多的一步操作就是"查找并判断下一条记录",因为都存在于同一个数据页,所以内存遍历对于CPU的开销可忽略。一个数据页可以存在上千个key,刚好下一条数据在下一个数据页而触发再次读取数据页的概率很低。所以这两种索引的效率几乎一样。
但是当进行增删改操作时,两种索引的效率就有明显的差距了。
因为唯一索引每个插入或者更新都要判断是否违反唯一约束,所以每次更新都要从内存中找到这个记录对应的数据页,没有的话需要从磁盘中读出,反正都要从磁盘读数据页了,还不如直接更新内存/磁盘,那么也就意味着change buffer失去意义,所以唯一索引使用change buffer 的话反倒会降低效率,所以只有普通索引能使用到change buffer。
浅谈mysql的change buffer
1.数据的更新过程
当mysql执行更新操作时,需要先更新一个数据页。如果这个数据页在内存中,则会直接更新,如果
这个数据页不在内存中,InnoDB会将这个更新操作缓存在change buffer中,当下次需要查询访问这个数据页的时候,会将数据页读入内存,然后执行change buffer中和这个数据页相关的操作,这样就保证了数据的逻辑正确性。
2.merge触发
将change buffer中的操作应用到原数据页,得到最新结果的过程称为merge。除了访问这个数据页会触发merge外,系统有后台线程会定期merge。在数据库正常关闭(shutdown)的过程中,也会执行merge操作。
3.使用change buffer的场景
只有普通索引的更新会使用change buffer,用来提高内存利用率,但是如果被更新的字段是唯一索引,就不会使用change buffer,因为更新操作会判断是否违反唯一约束的条件,必须将数据页读入内存才能判断,因此唯一索引不会使用change buffer,只有普通索引才能使用。
在日常业务场景中,对于写多读少的操作,使用change buffer的效果最好,例如账单类、日志类的操作,如果是读写操作都是很多的情况下,写完后将操作记录保存在change buffer中然后立即查询,这样会频繁触发merge,增加了IO的操作频率,这种场景使用change buffer并不能带来性能的提升,这种业务场景应该关闭change buffer。
4.change buffer的大小设置
在InnoDB中,可以通过参数innodb_change_buffer_max_size来设置,默认值是25,最大是50。例如当这个参数值是50 的时候,表示change buffer最多只能占用buffer pool的50%
5.索引的选择
唯一索引和普通索引在查询上并无区别,但是在更新操作上两者对性能的影响是不一样的,所以建议尽量使用普通索引。
MySQL-对Change Buffer的理解
Change Buffer的处理过程
对非唯一的普通索引的新增或更新操作,如果索引B+树的需要新增或更新的数据页不在内存中,则直接更新change buffer,等到后面需要使用这个数据页(真正读到内存中来)的时候,再根据change buffer在内存中做merge合并操作。
Change Buffer有什么好处?
先想想没有change buffer时候,在缓冲池中没有对应数据页时会怎么更新。概括来说,有两个步骤:
- 首先需要从磁盘中读取对应的数据页到内存中
- 然后更新内存中的数据页。
首先分析主键索引或者非主键索引中的唯一索引,插入或者更新的操作。
- 主键如果是自增的,只需要读取顺序读取磁盘中的页,然后插入最新的行即可。主键如果是非自增的或者是自己设置的值,那么可能需要做一次随机磁盘IO操作,读取到对应的页,做一下唯一性判断,然后插入数据即可。
- 针对非主键索引中的唯一索引,大概率需要做随机磁盘IO读取,然后判断唯一性,再插入对应的行。
所以对于主键索引和非主键的唯一索引,因为有唯一性判断,所以更新操作时,必须要从磁盘中读取数据页,判断唯一性,然后才能确定这个更新操作是否成功,即这个磁盘的IO操作是不可避免的。
对于非唯一索引来说,其实步骤也是类似的。但是因为不需要做唯一性判断,所以为了提高更新的性能,Mysql给出的解决方案就是使用change buffer来保存对非唯一索引的更新。也就是说,当需要更新非唯一索引时,直接操作change buffer,成功即可返回。
那么什么时候会真正更新数据页呢?有两种情况会触发:
- 被动:在后续的真正需要读这个非唯一索引时,把索引的数据页从磁盘读取到内存中,再通过change buffer做一个merge操作,merge操作以后,内存中的数据页就是最新的了。
- 主动:innoDB引擎中有线程会主动的定期做merge操作
业务实践
- 利用普通索引的change buffer特性,当业务场景中的写远大于读时,常见场景为日志表,当某些列必须建立索引时,可以考虑建立普通索引,提高写入性能。
- 如果业务场景的写之后立即伴随读,如果列的值是唯一的
- 那么其实建立普通索引是不合适的,因为写的过程,虽然利用了change buffer暂时提高了写的性能,但是在读的时候还是需要磁盘IO。可以考虑建立唯一索引,在索引写的时候,就提前读取数据到缓冲池中,提高读的性能。
MySQL -- Innodb中的change buffer
change buffer是一种特殊的数据结构,当要修改的辅助索引页不在buffer pool中时,用来cache对辅助索引页的修改。对辅助索引页的操作可能是insert、update和delete操作。等到相关的索引页被读入buffer pool中后,才会使用change buffer中的内容对辅助索引页进行修改(即merge操作)。
和聚集索引不同,辅助索引通常是不唯一的,插入辅助索引通常也是随机的。同样,对辅助索引的删除、更新也通常是不连续的。
等到相关的索引页被读入buffer pool中后,才会使用change buffer中的内容对辅助索引页进行修改(即merge操作)可以避免大量的磁盘随机访问I/O。
间歇性的,在系统空闲或关闭过程中,会执行purge操作,将新的索引页写入磁盘。purge操作一次写多个索引值会比每次修改后就立即写入磁盘的效率高。
对change buffer的merge可能需要好几个小时,如果被更新的辅助索引行比较多。在merge过程中,磁盘的I/O会增加,可能会引起其他查询的性能的降低。
merge操作也可能发生在事务提交后。事实上,即使在实例重启后,还会可能发生merge操作。
在内存中,change buffer会占用buffer pool的空间;在物理磁盘上,change buffer是system tablespace的一部分,所以对索引的修改在数据库重启后仍然存在change buffer中。
change buffer包含的特性也叫作change buffering,包含insert buffering、delete buffering、purge buffering。
change buffer中数据类型和总量由参数innodb_change_buffering和innodb_chagne_buffer_max_size配置。查看change buffer中数据的信息,可以通过show engine innodb status查看。
change buffer在老版本中被称作insert buffer。
1.监控change buffer
1
2
3
4
5
6
7
8
9
10
11
12
13
|
mysql> show engine innodb status\G ... ------------------------------------- INSERT BUFFER AND ADAPTIVE HASH INDEX ------------------------------------- Ibuf: size 1, free list len 0, seg size 2, 0 merges merged operations: insert 0, delete mark 0, delete 0 discarded operations: insert 0, delete mark 0, delete 0 Hash table size 4425293, used cells 32, node heap has 1 buffer(s) 13577.57 hash searches/s, 202.47 non-hash searches/s ... |
2.查看information_schema
information_schema.innodb_metrics提供了change buffer的统计信息名称和说明:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
mysql> select name , comment from information_schema.innodb_metrics where name like '%ibuf%' ; + -----------------------------------------+-------------------------------------------------------------+ | name | comment | + -----------------------------------------+-------------------------------------------------------------+ | buffer_page_read_index_ibuf_leaf | Number of Insert Buffer Index Leaf Pages read | | buffer_page_read_index_ibuf_non_leaf | Number of Insert Buffer Index Non-Leaf Pages read | | buffer_page_read_ibuf_free_list | Number of Insert Buffer Free List Pages read | | buffer_page_read_ibuf_bitmap | Number of Insert Buffer Bitmap Pages read | | buffer_page_written_index_ibuf_leaf | Number of Insert Buffer Index Leaf Pages written | | buffer_page_written_index_ibuf_non_leaf | Number of Insert Buffer Index Non-Leaf Pages written | | buffer_page_written_ibuf_free_list | Number of Insert Buffer Free List Pages written | | buffer_page_written_ibuf_bitmap | Number of Insert Buffer Bitmap Pages written | | ibuf_merges_insert | Number of inserted records merged by change buffering | | ibuf_merges_delete_mark | Number of deleted records merged by change buffering | | ibuf_merges_delete | Number of purge records merged by change buffering | | ibuf_merges_discard_insert | Number of insert merged operations discarded | | ibuf_merges_discard_delete_mark | Number of deleted merged operations discarded | | ibuf_merges_discard_delete | Number of purge merged operations discarded | | ibuf_merges | Number of change buffer merges | | ibuf_size | Change buffer size in pages | | innodb_ibuf_merge_usec | Time ( in microseconds) spent to process change buffer merge | + -----------------------------------------+-------------------------------------------------------------+ 17 rows in set (0.00 sec) mysql> |
information_schema.innodb_buffer_page提供了buffer pool中每个页的元数据,包含change buffer 索引页和change buffer位图页。
change buffer页中page_type.ibuf_index表示change buffer索引页;page_type.ibuf_bitmap表示change buffer位图页。
提醒:查看innodb_buffer_page会有很大的性能开销。最好是在空闲时间或测试环境执行。
比如,可以查看change buffer的索引页和位图页占据buffer pool的比例:
1
2
3
4
5
6
7
8
9
10
11
|
mysql> select ( select count (*) from information_schema.innodb_buffer_page -> where page_type like 'ibuf%' ) as change_buffer_pages, -> ( select count (*) from information_schema.innodb_buffer_page) as total_pages, -> ( select ((change_buffer_pages/total_pages)*100)) -> as change_buffer_page_percentage; + ---------------------+-------------+-------------------------------+ | change_buffer_pages | total_pages | change_buffer_page_percentage | + ---------------------+-------------+-------------------------------+ | 14 | 8192 | 0.1709 | + ---------------------+-------------+-------------------------------+ 1 row in set (0.05 sec) |
performance_schema还为高级性能监控提供了change buffer mutex的等待指令:
1
2
3
4
5
6
7
8
9
10
|
mysql> SELECT * FROM performance_schema.setup_instruments -> WHERE NAME LIKE '%wait/synch/mutex/innodb/ibuf%' ; + -------------------------------------------------------+---------+-------+ | NAME | ENABLED | TIMED | + -------------------------------------------------------+---------+-------+ | wait/synch/mutex/innodb/ibuf_bitmap_mutex | NO | NO | | wait/synch/mutex/innodb/ibuf_mutex | NO | NO | | wait/synch/mutex/innodb/ibuf_pessimistic_insert_mutex | NO | NO | + -------------------------------------------------------+---------+-------+ 3 rows in set (0.00 sec) |
3.配置change buffer
可以通过参数innodb_change_buffering来控制是否启用change buffer。
1
2
3
4
5
6
|
--all: 默认值。开启buffer inserts、delete-marking operations、purges --none: 不开启change buffer --inserts: 只是开启buffer insert操作 --deletes: 只是开delete-marking操作 --changes: 开启buffer insert操作和delete-marking操作 --purges: 对只是在后台执行的物理删除操作开启buffer功能 |
从5.6.2开始,参数innodb_change_buffer_max_size设置了change buffer可以占用buffer pool的百分比,默认是25,最大可以设置为50。
1
2
3
4
5
6
7
8
9
|
mysql> show variables like 'innodb_change_buffer_max_size' ; + -------------------------------+-------+ | Variable_name | Value | + -------------------------------+-------+ | innodb_change_buffer_max_size | 25 | + -------------------------------+-------+ 1 row in set (0.00 sec) mysql> |