以上面这张图为例:
如果我们需要找一个15岁的法外狂徒(误)张三:
select * from user where name = "张三" and age = 15;MySQL会先从第一个条件开始查找,找到名为“张三”的数据,此时会继续判断第二个条件,年龄为15岁,大于第一个指针中的10岁,且小于第二个数据中的20岁,所以会查找大于10岁且小于20岁的"张三"。
假设我们没有设置多个索引,只用名字来作为索引,那么此时的查找过程就是从8岁的张三开始,不断的向后遍历,直到找到这个15岁的张三。
也就是说,设置了多个索引,可以帮助MySQL更好的进行剪枝,更快速的定位到需要的数据。
只要满足最左前缀,就可以利用索引来加速检索。这个最左前缀可以是联合索引的最左N个字段,也可以是字符串索引的最左M个字符。
所以,索引的复用能力是我们在建立联合索引时候的一个评估标准。因为可以支持最左前缀,所以当已经有了(a,b)这个联合索引后,一般就不需要单独在a上建立索引了。因此,第一原则是,如果通过调整顺序,可以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的。
但是,联合索引也不是越长越好。我们在前面提到过,要尽可能的让N叉树的N比较大,这样树的高度会比较低,以此来减少磁盘的IO次数。如果联合索引包含的字段比较多,在页面大小固定的情况下,会造成N值的减少,反而会减慢效率。
4.2 索引下推继续上面的法外狂徒的例子。
假设我们的语句是这样的:
select * from user where name like "张%" and age = 15;很好理解,我们会觉得MySQL会从名字以“张”开头的数据开始遍历,然后判断年龄是否为15。
但是最左前缀有一个非常重要的原则:MySQL会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配。
也就是说,此时我们的查询,age这个索引是用不上的。
所以,在MySQL5.6之前,只要找到了符合以“张”开头的名字这个条件,就会通过这个数据的主键ID,进行回表的操作,然后查找这个数据的年龄是否为15。
而MySQL 5.6 引入的索引下推优化(index condition pushdown), 可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。也就是说,直到找到了以“张”开头的名字并且年龄为15,才会进行回表。
此外,在回表之前,如果使用了Multi-Range Read (MRR)这个策略,在取出主键后,回表之前,会在对所有获取到的主键排序。
4.3 覆盖索引还记得我们前面说到的吗,如果我们采用的是非主键索引,那么我们查到了这个数据之后,还需要根据叶子节点中的主键,再回表一次。
覆盖索引可以解决这个问题。比如我们前面查找“张三”的时候,我们也可以同时找到他的年龄。比如(a,b)这样的联合索引,在我们使用
select b form table_name where a = xxx;这么一条语句的时候,找到了符合条件的a,不需要通过主键来进行回表,找到b的值,而是会直接返回记录在这颗B+树中的值。也就是说,在这个查询里面,索引(a,b)已经“覆盖了”我们的查询需求,我们称为覆盖索引。
5 唯一索引与普通索引普通索引:加快对数据的访问速度
唯一索引:不允许重复的普通索引
5.1 查询我们先来分析查询方面的性能。
对于查询来说,如果这个是普通索引,那么在找到了符合条件的数据之后,会往后继续遍历,直到碰到不满足的数据为止。
如果是唯一索引,由于他的唯一性,只要找到了,那就直接返回就行,不需要继续往后遍历。
其实两者的性能差距微乎其微。
为什么呢?你可能会想:普通索引还需要继续遍历,有可能会更慢。但是,我们之前提到过,查询操作是需要把数据读到内存的,并且是以数据页的形式读到内存。而在内存中的遍历操作,速度方面的差距是特别小的。