点击上方蓝色“一点代码”,选择“设为星标”
更方便获取学习资源!
实际上,我们在面临选择这两种索引时,更多的会考虑业务数据的适用场景,如业务上是否可以确保唯一性?但当我们使用唯一索引后,又总会担心是否会造成性能的下降... 今天这篇文章,将深入解析普通索引和唯一索引在面临更新和查询两种场景时,底层的实现机制。假设我们在维护一张用户系统,每个人都有一个唯一的身份证号码,并且业务代码已经确保了不会写入两个重复的身份证号。 如果需要执行按照身份证号查询姓名,就会执行类似以下的SQL语句:
select name from CUser where id_card = 'xxxxxxxyyyyyyzzzzz';
由于用户表极高的数据量,我们应该考虑在id_card字段上建立索引。但由于身份证号字段比较大,并不建议作为主键。因此我们需要考虑选择使用唯一索引还是普通索引。
我们假设字段k上的值都不重复,来看看查询过程中不同的索引实现对性能的影响。
查 询 过 程
一. 查询场景 假定执行的语句:
select id from T where k = 5
这个查询语句在索引树上的查找过程,是首先通过B+树从树根开始,按层搜索到叶子节点(这部分我们可以认为数据页内部使用二分查找来定位记录)
- 对于普通索引来说,查找到满足条件的第一个记录(5,500)后,需要查找下一个记录,直到碰到的第一个不满足k=5条件的记录。
- 对于唯一索引,由于索引本身确定了数据的唯一性,因此查找到第一个满足条件的记录后,就会停止继续检索。
二. 更 新 场 景 说明普通索引和唯一索引更新操作上的性能差异,此处我们需要引入一个机制:change buffer 当需要更新一个数据页时,如果数据页在内存中就直接更新,而如果这个数据页不在内存中,在不影响数据一致性的前提下,InnoDB会将这些更新操作缓存在change buffer中,这样就不需要从磁盘读入这个数据页了。 在下次查询需要访问这个数据页时,将数据页读入内存,然后执行change buffer中与这个页有关的更新操作。通过这种方式来保证这个数据逻辑的正确性。 值得一提的是,虽然名字叫做change buffer,但实际上它是可以持久化的数据。 也就是说,change buffer在内存中有副本,也会被写入到磁盘上。 关于change buffer,MySQL官方介绍为:
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.
https://dev.mysql.com/doc/refman/5.7/en/innodb-change-buffer.html
将 change buffer 中的操作应用到原数据页,得到最新结果的过程称为 merge。 除了访问这个数据页会触发 merge 外,系统有后台线程会定期 merge。在数据库正常关闭(shutdown)的过程中,也会执行 merge 操作。 显然,如果能够将更新操作先记录在 change buffer,减少读磁盘,语句的执行速度会得到明显的提升。而且,数据读入内存是需要占用 buffer pool 的,所以这种方式还能够避免占用内存,提高内存利用率。 在什么条件下可以使用 change buffer? 对于唯一索引来说,所有更新操作都要先判断这个操作是否违反唯一性约束。比如要插入(4,400)这个记录,就要先判断表中是否已经存在了k=4的记录,而这必须要将数据读入内存才能判断。 如果都已经读入内存了,那直接更新内存会更快,就没必要使用change buffer了。 因此,唯一索引的更新操作就不能使用到change buffer了,实际上,也只有普通索引可以使用到。 change buffer使用的是buffer pool里的一部分内存空间 ,因此不能无限增大。关于buffer pool可参见官方文档: https://dev.mysql.com/doc/refman/5.7/en/innodb-buffer-pool.htmlchange buffer的大小可以通过参数:
innodb_change_buffer_max_size
来控制,当设置为50时,表示change buffer的大小最多能占用buffer pool的50%。
回到更新操作,我们在这张表中插入新纪录(4,400)时,InnoDB的处理流程如下:
- 对于唯一索引而言,找到k=3和5之间的位置,判断是否有冲突,然后插入这个值。
- 对于普通索引而言,找到k=3和5之间的位置,然后插入这个值。
- 对于唯一索引而言,需要将数据页读入内存,判断没有冲突,更新操作。
- 对于普通索引而言,则是将更新记录在change buffer中,操作结束。
————————————————
喜欢我就点个“在看”就 了- HashMap的核心解读
- JVM虚拟机内存探秘
- JVM之垃圾收集器与内存分配策略
- JVM - 类加载机制
- JVM - 双亲委派模型
- JVM - JIT即时编译器
- Redis - 核心能力解读
- RocketMQ - 生产者与消息发送源码解析
- RocketMQ - 消息存储与刷盘机制
- MySQL - 漫谈基础架构
- MySQL - 数据库索引工作原理深入解读
- MySQL - 数据库索引工作原理深入解读之联合索引