首先,mysql使用的B+树索引,B+树索引,是一种数据结构,类似于有序数组,不过,他不是用数组实现的,而是用的一种多叉树,并且是平衡的多叉树B+树,每个叶子节点通过引用连接起来,正好顺序排好了,就理解成有序数组吧,但是他的增删改的效率比有序数组高得多。
查询,就是二分搜索了,在一个有序数组里面查找一个数据,效率会很高。
索引的有序性以及执行过程是本篇博客的主要内容,会以实际的例子作为解释。本篇文章所用的mysql的版本是5.7.28
首先看下explain返回字端的解释:
SELECT识别符。这是SELECT的查询序列号
我的理解是SQL执行的顺序的标识,SQL从大到小的执行
1. id相同时,执行顺序由上至下
2. 如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
3. id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行
二、select_type
示查询中每个select子句的类型
(1) SIMPLE(简单SELECT,不使用UNION或子查询等)
(2) PRIMARY(子查询中最外层查询,查询中若包含任何复杂的子部分,最外层的select被标记为PRIMARY)
(3) UNION(UNION中的第二个或后面的SELECT语句)
(4) DEPENDENT UNION(UNION中的第二个或后面的SELECT语句,取决于外面的查询)
(5) UNION RESULT(UNION的结果,union语句中第二个select开始后面所有select)
(6) SUBQUERY(子查询中的第一个SELECT,结果不依赖于外部查询)
(7) DEPENDENT SUBQUERY(子查询中的第一个SELECT,依赖于外部查询)
(8) DERIVED(派生表的SELECT, FROM子句的子查询)
(9) UNCACHEABLE SUBQUERY(一个子查询的结果不能被缓存,必须重新评估外链接的第一行)
三、table
显示这一步所访问数据库中表名称(显示这一行的数据是关于哪张表的),有时不是真实的表名字,可能是简称,例如上面的e,d,也可能是第几步执行的结果的简称
四、type
对表访问方式,表示MySQL在表中找到所需行的方式,又称“访问类型”。
常用的类型有: ALL、index、range、 ref、eq_ref、const、system、NULL(从左到右,性能从差到好)
ALL:Full Table Scan, MySQL将遍历全表以找到匹配的行
index: Full Index Scan,index与ALL区别为index类型只遍历索引树
range:只检索给定范围的行,使用一个索引来选择行
ref: 表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
eq_ref: 类似ref,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单来说,就是多表连接中使用primary key或者 unique key作为关联条件
const、system: 当MySQL对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。如将主键置于where列表中,MySQL就能将该查询转换为一个常量,system是const类型的特例,当查询的表只有一行的情况下,使用system
NULL: MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值可以通过单独索引查找完成。
五、possible_keys
指出MySQL能使用哪个索引在表中找到记录,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用(该查询可以利用的索引,如果没有任何索引显示 null)
该列完全独立于EXPLAIN输出所示的表的次序。这意味着在possible_keys中的某些键实际上不能按生成的表次序使用。
如果该列是NULL,则没有相关的索引。在这种情况下,可以通过检查WHERE子句看是否它引用某些列或适合索引的列来提高你的查询性能。如果是这样,创造一个适当的索引并且再次用EXPLAIN检查查询
六、Key
key列显示MySQL实际决定使用的键(索引),必然包含在possible_keys中
实际使用的索引,若为null,则没有使用到索引。(两种可能,①没建立索引。②建立索引,但索引失效)。查询中若使用了覆盖索引,则该索引仅出现在key列表中。
七、key_len
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确型的情况下,长度越短越好,key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据定义计算而得,不是通过表内检索出的。
八、ref
列与索引的比较,表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
九、rows
估算出结果集行数,表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数
十、Extra(本博客按照此结果进行说明)
Distinct : 一旦MYSQL找到了与行相联合匹配的行,就不再搜索了
Range checked for each: 没有找到理想的索引,因此对于从前面表中来的每一个行组合,MYSQL检查使用哪个索引,并用它来从表中返回行。这是使用索引的最慢的连接之一
Using filesort: 发生了硬盘或内存排序,一般是由order by 或 group by触发的;典型的情况,排序的字段不是驱动表的字段,则会使用临时表将数据都添加进去,最后进行排序,如果数据大则硬盘排序,如果小则内存排序.
Using index :列数据是从仅仅使用了索引中的信息而没有读取实际的行动的表返回的,使用了覆盖索引优化
Using temporary :使用了临时表,通常和filesort一起发生. 典型情况:使用了派生表.
Using where: 数据库服务层从存储引擎提取了数据又进行了一次条件过滤
using index condition: ICP优化,从5.6之后提供.将过滤条件下推到存储引擎层执行,能更好的利用复合索引来过滤数据,减少IO.
Using index for group-by:使用了松散索引扫描
一:简单的索引说明
先从最简单的索引说起,就是单列索引,比如,一个表,有一个字段a,我们给这个字段a设置索引,当我们往表里面插入很多数据的时候,会同时在a字段的索引上插入对应的值,insert了4条数据,2,4,3,1,那么,索引上就是1,2,3,4这样排好了,当我们查询where a = 3的时候,直接在索引上二分搜索3在哪里啊,然后找到对应在数据库中的行的位置,不需要进行全表扫描了,哪怕有1亿个数据,当我们要查询a=?的时候,二分搜索a索引,也可以定位到在哪里,所以,索引查询速度很快的原因就是索引是排好序的。使用explain执行sql显示的结果为下图所示。图中表示的信息走索引。
解释:首先看Extra 是Using index。说明这条查询是个覆盖索引查询,并没有查询表,此次查询速度很快。possible_keys以及key的值都表示是使用了索引的,这个就利用了索引插入的有序性,快速定位到指定的数据。
从数据库表的角度来看,每一行添加,都是顺序添加的,可能a的值,上一行是1000,下一行就是20,在下一行是500,完全的随机乱序的,如果你查询a=666的行,因为表中的顺序行中的a的值,是没排序的,是随机的,再没有索引的情况下,你必须进行全表扫描。哪怕你找到了一个a=666的行,你也不能说,找到了,因为,很有可能后面顺序查找,还有a=666的行,你必须从第一行查询到最后一行,你才可以确定a=666这个where条件的所有可能的行,但是有了索引之后,每次插入顺序插入一行到表中,都会同步的记录这个索引,相当于说,顺序无序插表,但是,有序插索引。
你可能会说,顺序插入的效率是不是很低,这个不用你关心,因为b+树专门干这个。同时阿里巴巴开发规范也是这么说的。B+树的效果,就是一个有序的存储数据结构,不过他的增删改效率也有保证,不像有序数组,查询快,增删改就开销大了,实际上,B+树是从红黑树,B树发展而来,红黑树增删改查都比较快,不过,B+树更快,因为二分搜索的效率比红黑树左右节点判断决定路线的效率更好,并且,B+树是多叉树,它的高度很低,每个节点内,可能不只有左右子节点,而是每个节点可以存很多个值,这样,查询的时候,比红黑树效率低得多,但是,B+树叶子结点从左到右全部是有序的。总而言之就是,无序插行,有序插索引,索引查询用二分搜索。走索引查询,定位到第一个a=666之后,往下不断地继续搜索索引,如果找到比666大的,说明没了,全部找到了。因为索引的有序性。(下图为阿里巴巴规范中的参考说明,完全可以忽略索引导致的插入缓慢!)
二:联合索引的使用
新建一个表叫做abc表,里面有三个字段,a,b,c,其中,a,b,c三个字段,建立了一个联合索引,且都是int类型的。这里采用了a_b_c的联合索引。且表里的数据是无序插入的,所以表中的数据看起来是无序的,假设,我插入的数据是27行,就是111,112,113,121,122.。。。。。。333,我插入的是乱序的,因为索引会对联合字段进行排序。
最终他的
联合索引的排序是:()
111
112
113
121
122
123
131
132
133
211
212
213
221
222
223
231
232
233
311
312
313
321
322
323
331
332
333
我们就以此表数据进行实验分析
1⃣️:
explain select * from abc where a = 2;
注意看type=ref,这是一个很关键的信息,说明了查询引用了索引,我们来分析一下,为什么select * from abc where a = 2会走索引,我们要查a=2的行,首先,我们要明白,索引是怎么排序的,我要查的是a=2的所有的行,看这个索引排序图中,是不是只需要找到中间那9个2的就行了?是不是对a进行二分搜索,定位到a等于2,然后依次搜索,知道a等于3,就不满足了,我就可以立马锁定中间的那几(3)行
2⃣️:
select * from abc where b = 2;
这条sql会走索引吗?答案是:不会走索引。仔细看b这一栏。是不是这样轮回?--->111222333111222333,很明显他不是有序的。
如果你要找b=2的行,你找到了一个地方有几个2,你找到了一个地方有几个2。不能确定吧。
言外之意,如果你查找b=2的行,你必须从索引的第一行查询到索引的最后一行,而索引的大小和表的大小是一样的,相当于,你要把整个索引都搜索一遍,既然如此,还走啥索引?如果是这样,直接忽略索引,进行全表扫描,所以,这就是非常著名的左前缀规则
看到分析的结果就可以知道:但是possible_keys是null,key=index_a_b_c,说明了,这个查询根本就没有走索引,但是为什么,type=index呢?为什么会提示Using index呢?这里又会涉及一个概念了称之为覆盖索引。
覆盖索引:我的表是不是只有a,b,c三个字段,而我的联合索引也是abc三个字段的联合索引。
那么,这就意味着,索引中的数据,完全覆盖了表
select * from abc where b = 2;这个查询,即使用不到索引,也不需要全表扫描,因为这个时候的联合索引,等价于一张表了,联合索引覆盖了表的所有信息,因此,这个查询会直接在索引上查询,并不会走表。所以type=index,并且后面提示 Using index,Using where。注意,这里的where,是在索引上的where,而不是在表中的where。整个过程中不需要表的参与。我们重点关注的是,possible_keys 它的值为null,就说明了根本没有用到索引的定位功能。
三:在abc表中新增加一个字端d,并且这个b不在联合索引中,并且d字端中我也给了几个值。
1:
select * from abc where b = 2;
这条sql的Extra会是什么,执行的过程又会是什么?看下图所示
解释:由于我后面添加了d字段,而d字段并不在联合索引index_a_b_c上,所以select * 还包括了d字端的内容。因此,后面这个查询,会直接忽略索引,进行全表扫描,注意,type=ALl。并且,possible_keys=null,key =null。没有任何索引参加查询。查询的rows都是15行。所不同的是,一个是在表中进行扫描的。一个是在索引上进行扫描的。同样的,这个Using where,没有Using index了,这就是说,这个where发生在全表扫描上的where。并不是在索引上,也不是索引覆盖。
2:
selec a,b,c from abc where b =2;
这个又会如何?
解释:select的a,b,c三个字段,全部都在联合索引index_a_b_c中,虽然有d字段的存在,但是,我根本不查d字段啊,因此,全索引扫描,不走表了。同样的我select a,b 或者select a。都是一样的结果 。Using index说明的就是一个索引覆盖。Using where 代表的是索引当成了一个表进行过滤。
3:
select a,b,c from abc where a= 2 and b = 2;
会走索引吗?会使用b的索引吗?
这里的where条件是a=2 and b=2,首先,a等于2,立即锁定了中间a=2的几行数据,现在,在中间a等于2的那几行内部,执行b=2的查询。在a前缀已经匹配到的情况下,b会走索引吗?后面的b一定是排好序的,所以,b索引起作用 。
同理,如果是a=2,b=2,c=2。同样是a,b,c都走索引。下图的type=ref,说明使用了索引定位,possible_keys都有,不是null.注意,rows哪一行。显示的为1,索引锁定了那一行。这里要继续强调一下Using index的含义了。
Using index的意思,只有一个,那就是使用了覆盖索引,也就是只在索引中查询,不走表的意思。但是Using index并不代表会使用索引来定位,看是否使用索引定位,要看type=ref这一个,并且要看possible_keys是否为null,并且,要看rows是不是全索引扫描,再看key_ken(在不损失精确型的情况下,长度越短越好,key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据定义计算而得,不是通过表内检索出的)。再次强调,Using index那一个,并不代表使用了索引定位。Using index只有一个意思,那就是使用了覆盖索引 。
4:
select * from abc where a = 2 and b = 2;
直接看执行效果,大家也可以先猜猜一下执行的效果。
看所有的地方都一样,就是最后一个extra不同了,这里显示的Null。为什么是Null,这个Null又代表什么?暂时不说,你只需要明白一点,这里的*包含了d字段,肯定就不是Using index了。但是,后者依然使用了索引,type=ref,其他的信息,都表示它使用了索引。
5:
select a,b,c from abc where a = 2 and c = 2;
这条sql怎么使用索引呢?
解释:答案是a走索引,但是c不走索引。key_len =5,因为a是有序的,c在没有b的情况下,c的有序性保证不了。(如下图)如果换成select a,b,c from abc where a = 2 and b = 2 and c = 2;这个都会走索引。此处:左前缀规则深入理解
6:以下这两个sql与第5个第区别就是多了一个d字端的查询,那这个索引又会如何执行?
select * from abc where a = 2 and c = 2;
select * from abc where a = 2 and c =2 and b=2 ;
都有type=ref,说明都使用了索引定位,key_len一个是5,一个是15,再看ref,一个const,下面3个const,再看rows锁定的行,一个是3,一个是1,但是最后一个Extra,Using index condition 另一个是null。
注意:这里重点说一下这个Using index condition了,看着两个对比,一个是select *,上一个是select a,b,c,Extra不一样了,其他的都一样。解释如下:先说一下第5⃣️中的第一个。这里出现了Using index,说明了,这里使用了覆盖索引,但是,这里有一个where,这个where,显然是对c=2的一个过滤,也就是说,这个where时发生在覆盖索引上的where,这里把覆盖索引当成了一个表看到,不用回表了,这个where,针对的是c=2的过滤(你要把覆盖索引当成一张表),在看第6⃣️个第第一个图:这里使用的select * 包含了d字段,肯定不可能是Using index了,这里的查询必须回表查询d的值,c=2也没有走索引,但是索引中包含了所有关于2的值,在这个a=2锁定的3行rows中,是不是可以拿到所有的关于c的值,
因此,mysql引擎并不会贸然的直接让c=2这个发生在表中进行查询,而是在索引定位了3行之后,然后把行位置发送到表中进行定位的时候,再做一个过滤c=2的处理,这个过程,称之为Using index condition。我用一个很简单的说法来形容,那就是,不需要去表中where c=2的过滤,在索引上就可以提前完成过滤,但是,你肯定会说,那应该有一个Using where 啊?为什么不是Using where,注意where只可以发生在表中,表中才有where,而现在这个联合索引他是覆盖索引吗?并不是,此刻不是一个表,因为a=2已经锁定了对应的那几行。不是全索引扫描。
但是第5⃣️步的第二个就是全索引扫描,等同于覆盖索引,就是一张表,不用回表了,所以会有where。(你的Using where只能使用在表中过滤)。虽然,c没有参与定位,但是,他可以在发送到表之前,提前做过滤,但是这个过滤,又不是where过滤,而是索引上发生的一些提前优化,(注意:这个特性只在mysql5.6之后才有)有了这个Using index condition的优化,就可以提前锁定具体哪些行是需要查询的,定位到表的时候,不用再where过滤了。)不需要表中进行过滤了。这一块需要值得注意。
7:
explain select * from abc where a=2 and b=2;
看这个,我用了select *,a和b都使用了联合索引,首先,select *,肯定要回表,因为包含d,d并不在索引中。因此,extra肯定不可能是Using index,a = 2 and b = 2,这全部被联合索引定位到了,是不是没有再过滤的需要了?因此不需要Using index condition,定位到了数据库表中,还需要进行where操作吗,很明显不需要,所以Using where也不会有。最终他就是null。
8:
explain select * from abc where a = 2 and b = 2 and d = 2;
这里,a和b被联合索引锁定了,但是还有一个d=2,联合索引还能帮忙优化吗,可以帮你在索引数据中排除d不等于2的吗。显然,d不在联合索引中。因此,联合索引只做了精确定位这一件事。没有做进一步过滤优化,然后定位到了1行的rows的表中了,在表中是不是要进行where d=2的操作?对着1行进一步的过滤,因此,就有了Using where了。显然没有Using index,也不会是Using index condition了
9:
explain select a,b,c from abc where a = 2 and b = 2 and d = 2;
虽然我是用了select a,b,c,看上去都在联合索引中,但是有d=2啊,你必须回表。不可能是索引覆盖,即不是 Using index,只能是Using where。
10:
explain select a,b,c from abc where b = 2 and c = 3;
using index是因为这里是覆盖索引,但是,这里的覆盖索引相当于是一个表了,不会再回表了。为什么where?因为b=2,c=2,这里就是where!覆盖索引相当表,所以有where。并且索引根本没有效果。possible_keys=null,ref =null,这里的联合索引,根本就没有定位的作用,他就是一张表。
11:
explain select * from abc where a = 2;
为啥是Extra是null?
首先,a=2,是索引精确确定的,后续没有索引优化过滤的步骤了,并且,回表之后,不会执行where,索引确定是哪些行,就是哪些行。后续的回表也没有过滤的操作,所以没有Using where,也不是Using index.因为不是覆盖索引。也么有索引优化的过滤,最后只能是null。
12:
explain select a,b,c from abc where a = 2 and c = 2 and d = 2;
先condition索引筛c了,回表where筛d
三:排序的索引使用过程(索引的排序也是对sql的一种优化),合理的运用索引的有序性,可以提高查询的速度。
1:
explain select * from abc where a = 1 and b > 1 and c = 3;
注意,这里的type变成了range,所以,这里没有ref,这是一个范围索引,a=1可以确定row行,b>1进一步确定了4行,c的顺序无法保证,因此,ken_len是10,只有2个联合索引字段起作用了,回表的时候,c可以被提前优化一下,但是,这样回表之后,就不存在where了,因此这里,只有Using index condition
2:
explain select * from abc where a = 1 and c = 2 order by b;
b已经是排好序的了,不需要进行排序了,select * 包含d 需要回表,但是回表前,c=2已经对索引进行了优化。
3:
explain select * from abc order by a;
虽然,这里没有任何where条件,这是一个全表查询,但是,有一个按a排序的需求,而联合索引中,a就是排好序的。
看图,执行了全表扫描,type=all,并且,排序使用了Using filesort,说明了进行了内存排序功能,根本没用索引。
如果一个查询根本就不用到索引,他当然就不会使用索引来排序了。所以会出现内存排序的情况(也就是消耗很大)!
你只有在保证一个查询必须使用索引的前提下,才可以使用它附带的排序功能,select * 没有任何的where,直接全表扫描。索引没有被利用。索引的使用的条件是:先看where ,和join on再看列。如果没有,就说直接查表了。覆盖索引会使用索引。
4:
explain select * from abc where a = 1 order by b;
Using index condition到底是什么意思?是不是回表之前的提交优化。很明显,order by b就是对回表前的优化。
5:注意了,现在我把abcd四个字段全部建成索引,索引现在是index_a_b_c_d
explain select * from abc where a = 1 and d = 3 order by b,c;
解释:因为,a=1之后,b和c本来就是排好序的了,而这个where说的是d。但是不会回表,因为索引包括了这些数据 。是一个覆盖索引
6:此刻我在表中增加一个e字段,但是e字段并不是索引中段字端,只是个普通段int 类型段字段。
explain select * from abc where b = 1 order by a,c;
b=1,排除了索引的使用(左前缀原则),是全表扫描。e不在索引字端中。索引都没有走,肯定不会对a,c进行排序。所以会采用内存排序,因为,a有序,但是c,不能保证有序。如果换成a,b就不会出现filesort。
7:
explain select * from abc where a = 1 order by a,b;
看图的答案,查询中包含e字端,此刻需要回表,这个时候,where a=1是一个索引。但是也优化了,ab的顺序。
如果是order by a,c则是Using filesort ,Using index condition;
针对于group by的查询暂时没有去研究,这篇博客也是我跟一个大佬所学的,此处作为一个学习的笔记分析于大家,如果有什么不妥之处,请评论指出。
另外附以下阿里的开发规范对于索引的一下建议要求