MySQL索引详细深入解读

/ 默认分类 / 0 条评论 / 177浏览

一.什么是mysql索引

MySQL索引是一种数据结构,用于提高对MySQL数据库表中数据的检索速度。它类似于书籍的目录,可以帮助数据库系统更快地定位到存储在表中的特定行。索引是通过在一个或多个列上创建的数据结构,以提高对这些列数据的搜索和排序效率。 索引的工作原理类似于字典的目录。当你想要查找字典中某个单词的定义时,首先会翻开字典的目录,找到对应的页码,然后直接翻到该页码上查找单词的定义。而不是从第一页开始逐页查找,这样可以大大提高查找速度。

二.mysql索引的分类

MySQL支持两种存储引擎分别是innoDB和MyISAM,默认使用innoDB存储引擎,下面我们也是基于innodb来进行索引介绍。

从物理存储形式上可以分为两类

从索引的数据结构上看,可以进行如下分类

R树(R-tree)是一种多维数据结构,特别适用于索引空间数据,如地理信息系统(GIS)中的地理数据类型(如点、线、多边形等)。R树是由Antonn Guttman在1984年提出的,用于解决空间数据索引和查询的问题。因为对我来说基本用不到,详细的R树相关的知识点大家可以自行搜索相关文档。

对于一个服务端开发来说,我们更多的需要了解下面几种具体的mysql索引类型

primary key(字段名)

普通索引与主键索引的区别在于,普通索引的叶子节点存放的不是行数据,而是主键值。

CREATE INDEX 索引名 ON 表名(字段名); 
或
ALTER table 表名 ADD INDEX 索引名(字段名)

与普通索引类似,不同点在于唯一索引的索引列的值必须唯一,但允许有空值,这点与主键不同(主键索引列的值唯一,但不能为空)。如果是多个字段组成的联合唯一索引,则列值的组合必须唯一,创建方法与普通索引类似。

CREATE UNIQUE INDEX 索引名 ON 表名(字段名);
或
ALTER table 表名 ADD UNIQUE INDEX 索引名(字段名)

使用多个列字段建立的索引,称为联合索引,也叫组合索引。

CREATE INDEX 索引名 ON 表名(字段名1,字段名2);

三.索引原理详细分析

3.1 聚集索引(主键索引)结构

从上面的介绍,我们知道,聚簇索引就是按照每张表的主键来构造一颗B+树,叶子节点存放的就是整张表的行数据。(在innodb中,聚集索引也就是主键索引)也就是类似下面的结构: image.png

和之前我们介绍B树时一样,典型的B树索引结构,只有叶子节点才存储实际的数据,索引节点只存了主键id,也就是关键字。另外,主键索引特殊的地方在于,叶子节点不单单只是存储了数据,而是存储了当前主键对应的表的整个行数据。 也就是说,当我们查询到指定的叶子节点的数据之后,就等于完成了整个查询操作,因为当前数据包含了所有字段。

3.2 非聚集索引(普通索引)

当一个查询使用普通索引时,MySQL会首先使用非聚集索引(普通索引)找到满足查询条件的行的主键(或者叫做记录指针),然后再使用这些主键值去聚集索引(主键索引)中查找对应的数据行。这个过程被称为“回表”(也叫“二次查找”),因为查询首先使用非聚集索引进行一次查找,然后通过找到的主键值再在聚集索引中进行第二次查找,以获取完整的数据行。 也就是说如果一个查询走的是普通索引,那么需要先通过非聚集索引找到对应数据的主键id,然后再使用主键id值走聚集索引,查到相应的数据。当然,如果我们查询的字段就只是普通索引的字段,那么mysql会避免再次查询聚集索引,因为非聚集索引中已经包含当前需要的数据。

下面我画了一张结构图,表示聚集索引回表查询的过长。 image.png

3.3 非聚集索引(组合索引)

组合索引(联合索引)的数据结构也是B+树,不同的是,二叉树的非叶子节点不是单个数值作为关键字,而是键值对。假设我们的组合索引是(a,b,c) 那么每个节点的数据就是(1,2,4)或者(2,3,6)或者(5,3,2).类似的形式。 下面我画了一张组合索引的大致数据结构图: image.png 可以看到,键值对数据的排序也是有序的,遵循BST的插入规则,对于每个节点,a值是有序插入的,另外,在a值相同的前提下,b值也是有序的。

最左匹配原则

假设表的组合索引是(a,b,c),下面我列举了相应的查询和索引利用情况:

#利用到索引(a,b,c),用上a,b,c
select * from t where a=1 and b=1 and c =1;   
#利用到定义的索引(a,b,c),用上a,b
select * from t where a=1 and b=1;     
#利用到定义的索引(a,b,c),用上a,c(mysql有查询优化器,会调整where条件匹配顺序)
select * from t where b=1 and a=1;     
#利用到定义的索引(a,b,c),用上a
select * from t where a=1;     
##不可以利用到定义的索引(a,b,c)
select * from t where b=1 and c=1;     
#可以利用到定义的索引(a,b,c),但只用上a索引,b,c索引用不到
select * from t where a=1 and c=1;     

从上面的分析其实不难看出,所谓最左匹配原则,其实就是,当执行查询时,如果查询条件只涉及到组合索引的左前缀列,MySQL会使用这个索引来加速查询。这里的左前缀其实就是查询条件式a,b或者a,b,c或者a。另外如果查询条件是a,c,b mysql优化器会将查询条件顺序调整为a,b,c. 上面这种情况是可以利用(a,b,c)这个组合索引的。 但是如果查询条件是b,c 这样就无法利用索引了,因为不符合左前缀。

其实从上面我们画的索引结构也能看出来,索引的第一个值一定需要作为条件,因为只有第一个索引值才是全局有序的,如果第一个索引值不在查询条件中,那么就无法进行BST查询。

在使用组合索引进行查询的时候,如果遇到范围查询(>、<、between、like)就会停止后续所有的索引匹配了。

like 后的关键字的前面不能使用%(例如:name like "%三"),只能在关键字的后面加上%,因为索引是从左至右匹配的,如果在前面加上%就无法进行大小匹配了,等于第一个就是范围匹配,后续的数据都是无序的了,下面也会详细解释。

假设已经建立了组合(a,b,c,d),那么下面的查询就会出现索引匹配终止:

..... where a = 10 and b = 20 and c >100 and d = 5

上面的where查询条件,只有a, b, c能使用到联合索引(a,b,c,d),d的条件无法使用索引,因为c>100属于范围查询,将后面d的索引匹配给中断了。 从索引结构上来看,如果匹配到b=20,找到了明确的几个节点数据,然后需要判断c>100,这个时候,因为查询条件是一个范围,所以我们无法在b=20的键值数据中,使用二分查找快速匹配定位到数据(如果是c=40,那么就可以基于索引来匹配了),并且,当匹配完a和b之后,根据前面我们说的在a值相同的前提下,b值也是有序的。那么因为c是范围条件,所以可能是10,20,30等等,所以此时匹配后剩余的数据中,d一定是无序的,所以无法使用二分查找对非叶子节点进行索引数据查询了,只能对剩下的数据进行全表扫描,查出符合剩余条件的数据。

首先一定要搞明白,其实组合索引的索引检索过程和普通索引是类似的,只是对于组合索引,需要先匹配第一个键值,也就是第一个查询条件,根据BST进行查询,然后等于确定了第一次查询后筛选的范围数据了,然后在这个基础上进行第二个键值的数据筛选,以此类推。

关于索引失效的场景,参考下面这篇腾讯云的技术博客,我这里就不一一列举了。