假如有一张表,它需要存储用户的身份证号以及姓名,业务层已经保证了身份证号唯一,此时有以下查询:

1
select name from table where id_card = "xxxxyy"

为了提高效率,我们会在id_card上面加索引,那么到底是加唯一索引还是普通索引呢?我们可以从这两种索引对查询语句以及更新语句的性能影响来进行分析。

查询过程

假设执行的语句是select id from T where k=5。对于两种索引执行过程如下:

普通索引:他会先找到k = 5的这一条数据,然后会继续往下找,直到第一条不满足k = 5的数据。

唯一索引:因为索引保证了唯一性,所以碰到第一条为5的记录后,就可以直接返回。

这一点的不同带来的性能差距很小。因为InnoDB的数据是以数据页为单位来读的,意味着读一条数据,会将那条数据所在的页都加载到内存当中,所以读取k = 5的时候,大概率后续几条数据会在同一个数据页中,此时性能差异很小。一个特殊情况是后续几条数据和k = 5不在同一个页中,那么此时效率会有点低。

更新过程

change buffer

MySQL在更新数据时,如果数据页不在内存中,就会先将更新操作写入change buffer中,等到下次查询该数据时,将change buffer中的操作执行到具体的数据。

除了查询的时候会进行数据同步外,后台也会有线程定期对change buffer中的操作执行到具体的数据。

change buffer在内存中也有拷贝,也会被写入到磁盘。

使用条件

对于唯一索引来说,在更新数据前他需要先判断该操作是否满足唯一性约束,进行判断就意味着必须要将数据页读到内存。这也就意味着唯一索引在进行修改时,修改数据所在的数据页一定在内存中。

也就是说,change buffer只有普通索引才可以使用。

针对于一个更新,分为两种情况:

如果要更新的数据在内存中,那么两种索引的更新情况类似,只不过唯一索引多了一次判断,影响不大。

但是如果数据不在内存中,那么唯一索引需要先把数据查询出来放入内存,然后再更新,而普通索引写入change buffer即可。

使用场景

由于在进行合并的时候,change buffer记录的变更越多(也就是这个页面上要更新的次数越多),收益就越大。

因此,对于写多读少的业务来说,页面在写完以后马上被访问到的概率比较小,此时change buffer的使用效果最好。

change buffer 和 redo log

这两种设计原理,都是为了减少随机的磁盘读取。现在考虑执行下面的语句:

1
insert into t(id,k) values(id1,k1),(id2,k2);

这里我们假设k1所在的数据页在内存中,而k2所在的数据页不在内存中。那么这条更新语句要做的处理如下:

1、因为k1数据页在内存中,则直接更新k1数据所在的数据页即可。

2、因为k2数据页不在内存,就在change buffer中记录类似于我要插入k2这条记录即可。

3、将上述两个动作记录redo log中。

这样,插入就执行完成了。两次内存操作,一次磁盘操作。

在执行完上面的插入操作后,如果继续执行下面语句:

1
select * from t where k in (k1, k2)。

由于k1所在的数据页还在内存当中,直接从内存中读取返回,而k2所在的数据页不在内存中,则从磁盘读取,然后于change buffer整合,返回正确的结果。

总结

所以,如果要简单地对比这两个机制在提升更新性能上的收益的话,redo log 主要节省的是随机写磁盘的IO消耗(转成顺序写),而change buffer主要节省的则是随机读磁盘的IO消耗。

增删改操作都涉及到从磁盘中读取数据页加载到内存,而使用了change buffer后,直接将增删改操作记录到change buffer中即可,下次做查询操作时进行整合即可,减少读磁盘的次数。

而redo log是在增删改操作时,先将操作记录redo log,然后将数据在数据库缓存(buffer pool)中修改。由于这部分数据是在内存当中,没有同步到磁盘,而此时数据库异常关闭,可以通过redo log来恢复这段期间的数据,保证数据的一致性。

一个小疑问

我最开始比较疑惑为什么插入操作会涉及到考虑数据页是否在内存当中,原因是MySQL在插入数据时,会先找到这条语句所对应的数据页,然后把数据插入到对应的数据页中。所以说,即使是插入操作,也要确保数据页在内存中。如果在插入时使用了 Change Buffer 技术,那么可以将这些插入操作暂时缓存到 Change Buffer 中,等到后续查询这个数据页的时候,再将 Change Buffer 中的数据一次性刷新到对应的数据页中,这样就可以减少磁盘 IO 操作,提高系统的性能。

参考

《MySQL45讲》