点击蓝色“架构文摘”关注我哟
加个“星标”,每天上午 09:25,干货推送!
为感谢大家的支持,特送 10个 240GB SSD固态硬盘,加快你的idea!
详情查看今日推送文章:
《加速你的IDE!粉丝免费领取240GB SSD固态硬盘!》
by 架构君
索引
索引常见的类型有哈希索引,有序数组索引,二叉树索引,跳表等等。本文主要探讨 MySQL 的默认存储引擎 InnoDB 的索引结构。
InnoDB的索引结构
在InnoDB中是通过一种多路搜索树——B+树实现索引结构的。在B+树中是只有叶子结点会存储数据,而且所有叶子结点会形成一个链表。而在InnoDB中维护的是一个双向链表。
![](https://i-blog.csdnimg.cn/blog_migrate/490de7912d756e634ecfb56154728e3e.png)
你可能会有一个疑问,为什么使用 B+树 而不使用二叉树或者B树?
首先,我们知道访问磁盘需要访问到指定块中,而访问指定块是需要 盘片旋转 和 磁臂移动 的,这是一个比较耗时的过程,如果增加树高那么就意味着你需要进行更多次的磁盘访问,所以会采用n叉树。而使用B+树是因为如果使用B树在进行一个范围查找的时候每次都会进行重新检索,而在B+树中可以充分利用叶子结点的链表。
在建表的时候你可能会添加多个索引,而 InnDB 会为每个索引建立一个 B+树 进行存储索引。
比如这个时候我们建立了一个简单的测试表
create table test(
id int primary key,
a int not null,
name varchar,
index(a)
)engine = InnoDB;
这个时候 InnDB 就会为我们建立两个 B+索引树,一个是 主键 的 聚簇索引,另一个是 普通索引 的 辅助索引。
![](https://i-blog.csdnimg.cn/blog_migrate/74e00e82fe62b8c605e1e18691f9d97b.png)
可以看到在辅助索引上面的叶子节点的值只是存了主键的值,而在主键的聚簇索引上的叶子节点才是存上了整条记录的值。
回表
所以这里就会引申出一个概念叫回表,比如这个时候我们进行一个查询操作
select name from test where a = 30;
我们知道因为条件 MySQL 是会走 a 的索引的,但是 a 索引上并没有存储 name 的值,此时我们就需要拿到相应 a 上的主键值,然后通过这个主键值去走 聚簇索引 最终拿到其中的name值,这个过程就叫回表。
我们来总结一下回表是什么?MySQL在辅助索引上找到对应的主键值并通过主键值在聚簇索引上查找所要的数据就叫回表。
索引维护
我们知道索引是需要占用空间的,索引虽能提升我们的查询速度但是也是不能滥用。
比如我们在用户表里用身份证号做主键,那么每个二级索引的叶子节点占用约20个字节,而如果用整型做主键,则只要4个字节,如果是长整型(bigint)则是8个字节。也就是说如果我用整型后面维护了4个g的索引列表,那么用身份证将会是20个g。
所以我们可以通过缩减索引的大小来减少索引所占空间。
当然B+树为了维护索引的有序性会在删除,插入的时候进行一些必要的维护(在InnoDB中删除会将节点标记为“可复用”以减少对结构的变动)。
比如在增加一个节点的时候可能会遇到数据页满了的情况,这个时候就需要做页的分裂,这是一个比较耗时的工作,而且页的分裂还会导致数据页的利用率变低,比如原来存放三个数据的数据页再次添加一个数据的时候需要做页分裂,这个时候就会将现有的四个数据分配到两个数据页中,这样就减少了数据页利用率。
覆盖索引
上面提到了 回表,而有时候我们查辅助索引的时候就已经满足了我们需要查的数据,这个时候 InnoDB 就会进行一个叫 覆盖索引 的操作来提升效率,减少回表。
比如这个时候我们进行一个 select 操作
select id from test where a = 1;
这个时候很明显我们走了 a 的索引直接能获取到 id 的值,这个时候就不需要进行回表,我们这个时候就使用了 覆盖索引。
简单来说 覆盖索引 就是当我们走辅助索引的时候能获取到我们所需要的数据的时候不需要再次进行回表操作的操作。
联合索引
这个时候我们新建一个学生表
CREATE TABLE `stu` (
`id` int(11) NOT NULL,
`class` int(11) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `class_name` (`class`,`name`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8
我们使用 class(班级号) 和 name 做一个 联合索引,你可能会问这个联合索引有什么用呢?我们可以结合着上面的 覆盖索引 去理解,比如这个时候我们有一个需求,我们需要通过班级号去找对应的学生姓名 。
select name from stu where class = 102;
这个时候我们就可以直接在 辅助索引 上查找到学生姓名而不需要再次回表。
总的来说,设计好索引,充分利用覆盖索引能很大提升检索速度。
最左前缀原则
这个是以 联合索引 作为基础的,是一种联合索引的匹配规则。
这个时候,我们将上面的需求稍微变动一下,这时我们有个学生迟到,但是他在门卫记录信息的时候只写了自己的名字张三而没有写班级,所以我们需要通过学生姓名去查找相应的班级号。
select class from stu where name = '张三';
这个时候我们就不会走我们的联合索引了,而是进行了全表扫描。
为什么?因为 最左匹配原则。我们可以画一张简单的图来理解一下。
![](https://i-blog.csdnimg.cn/blog_migrate/f97a1db72fafd3d10414bcf28fbd91d8.png)
我们可以看到整个索引设计就是这么设计的,所以我们需要查找的时候也需要遵循着这个规则,如果我们直接使用name,那么InnoDB是不知道我们需要干什么的。
当然最左匹配原则还有这些规则:
全值匹配的时候优化器会改变顺序,也就是说你全值匹配时的顺序和原先的联合索引顺序不一致没有关系,优化器会帮你调好。
索引匹配从最左边的地方开始,如果没有则会进行全表扫描,比如你设计了一个(a,b,c)的联合索引,然后你可以使用(a),(a,b),(a,b,c) 而你使用 (b),(b,c),(c)就用不到索引了。
遇到范围匹配会取消索引。比如这个时候你进行一个这样的 select 操作
select * from stu where class > 100 and name = '张三';
这个时候 InnoDB 就会放弃索引而进行全表扫描,因为这个时候 InnoDB 会不知道怎么进行遍历索引,所以进行全表扫描。
索引下推
我给你挖了个坑。刚刚的操作在 MySQL5.6 版本以前是需要进行回表的,但是5.6之后的版本做了一个叫 索引下推 的优化。
select * from stu where class > 100 and name = '张三';
如何优化的呢?因为刚刚的最左匹配原则我们放弃了索引,后面我们紧接着会通过回表进行判断 name,这个时候我们所要做的操作应该是这样的
![](https://i-blog.csdnimg.cn/blog_migrate/8b6dedaefad53ca7de3955d0d42a185b.png)
但是有了索引下推之后就变成这样了,此时 "李四" 和 "小明" 这两个不会再进行回表。
![](https://i-blog.csdnimg.cn/blog_migrate/d0014490eec6c4ed16183596068bbc20.png)
因为这里匹配了后面的name = 张三,也就是说,如果最左匹配原则因为范围查询终止了,InnoDB还是会索引下推来优化性能。
一些最佳实践
哪些情况需要创建索引?
频繁作为查询条件的字段应创建索引。
多表关联查询的时候,关联字段应该创建索引。
查询中的排序字段,应该创建索引。
统计或者分组字段需要创建索引。
哪些情况不需要创建索引?
尽量选择区分度高的列作为索引。
不要对索引进行一些函数操作,还应注意隐式的类型转换和字符编码转换。
尽可能的扩展索引,不要新建立索引。比如表中已经有了a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。
多考虑覆盖索引,索引下推,最左匹配。
作者:FrancisQ
https://juejin.im/post/5db19103e51d452a300b14c9
推荐阅读:
-END-
架构文摘
ArchDigest
架构知识丨大型网站丨大数据丨机器学习
如有收获,点个在看,诚挚感谢![](https://i-blog.csdnimg.cn/blog_migrate/860daef08ca6449ceb6e58d9f561bee0.png)