前言
本文介绍MySQL
的更新缓存Change Buffer
,以及唯一索引和普通索引如何选择。
唯一索引和普通索引的选择
查询过程
唯一索引下,查询索引树,找到第一条匹配的行就返回;
普通索引下,查询索引树,找到第一条匹配的行之后,继续往下遍历,直到第一条不匹配的行为止,再返回。
即使匹配的行跨了数据页,但一个数据页默认16KB
,每行只存储一个key
且是整数,可以存储近千个。那么普通索引下,最多也是多加载一次数据页。所以唯一索引和普通索引的查询效率基本相同。
更新过程
首先介绍下MySQL
的更新缓存Change Buffer
,它使用的是MySQL Buffer Pool
的存储空间,可以设置Change Buffer
在Buffer Pool
中的占比,MySQL5.6
默认为25%
,最多可以开到50%
。
顾名思义,Change Buffer
就是用来存储更新操作的,使得更新操作不需要加载对应的数据页,直接更新到内存。当对Change Buffer
中的行进行查询时,就会将原始数据页加载到内存,并将缓存应用到原始数据页,这个就是merge
过程。Change Buffer
的merge
触发时机:
- 当原始数据页加载到
Buffer Pool
时 - 后台线程定期
merge
- 数据库正常关闭时
Change Buffer
的刷盘触发时机:
- 数据库空闲时,会定时持久化
- 数据库缓冲不够用时
- 数据库正常关闭之前
redo log
写满时
你或许会疑问“万一在刷盘之前数据库宕机了,那之前的更新不就丢失了么?”
答案是否。简单分析是,因为将更新操作写到Change Buffer
后,还会将更新操作写到redo log
并持久化到磁盘,数据库宕机重启之后,会将之前的更新缓存数据恢复到内存。
具体分析如下:
(1)change buffer
写入,redo log
虽然做了fsync
但未commit
,binlog
未fsync
到磁盘,这部分数据丢失
(2)change buffer
写入,redo log
写入但没有commit
,binlog
以及fsync
到磁盘,先从binlog
恢复redo log
,再从redo log
恢复change buffer
(3)change buffer
写入,redo log
和binlog
都已经fsync
.那么直接从redo log
里恢复。
接下来分析两种索引下的更新操作:
唯一索引下,查询索引树,加载对应的数据页到内存,判断是否违反一致性约束,再更新;
普通索引,查询索引树,直接更新内存中的Change Buffer
。
因为唯一索引需要判断更新操作是否违反一致性约束,所以必须加载数据页,也就用不到Change Buffer
,即Change Buffer
只用于普通索引。
从上面的分析可见,在更新多于读取操作的情况下,普通索引的更新操作效率要高于唯一索引。但如果是更新之后就有查询的场景,那么Change Buffer
不但没有起到提效作用,反而占用的缓冲空间。所以,这种情况下,一般会关闭Change Buffer
来避免它的副作用。
总结
总结来说,如果业务需要数据库来对数据进行唯一性约束,那么优先还是考虑唯一索引;否则,如果是更新远多于读取操作的业务场景,比如归档,日志等,考虑用普通索引代替唯一索引,可以提高内存命中率和提高更新效率。但如果是更新之后就有查询的场景,则建议关闭Change Buffer
,来避免它的副作用。