这里举几个case,加强一下索引原理的理解。以下case均基于之前的employees 表。


EXPLAIN SELECT * FROM employees WHERE right(name,3) = 'LiLei'; 

MySQL索引使用case的原理分析_MySQL

不要在where条件后对索引的列进行操作(计算,函数,手动or自动类型转换),因为不走联合索引,那原理是什么呢?


想象一下辅助索引树!

如果在name上加了函数,就无法直接在索引树中定位到指定name的节点上,想象一下,索引树节点中只有必须先要知道name,才能迅速往下查找,如果加函数,相当于几乎都要将每个节点遍历一遍做函数处理再对比,还不如直接聚簇索引从头遍历比对。


EXPLAIN SELECT * FROM employees WHERE name= 'Bill' AND age > 22 AND position ='manager'; 

MySQL索引使用case的原理分析_innodb引擎_02

对于联合索引,MySQL不会使用范围查询后面的索引字段。这里虽然走了联合索引,但key_len结果78,说明没有position索引字段。原理呢?


想象一下辅助索引树!

MySQL索引使用case的原理分析_B+Tree_03



这里使用到了name,可以定位到指定name的节点,同一个name的age是有序的,因此查询使用到了age,但age>22是一个范围,但在这个范围内的所有的postion可能是无序的(age=23和age=24的position不一定有序),因此不能直接定位到底哪个是符合要求的,需要把满足name='lilei'和age>22的所有的节点都扫描以便才能找到positinotallow='manager'的节点。


EXPLAIN SELECT * FROM employees WHERE name != 'LiLei'; 

MySQL索引使用case的原理分析_B+Tree_04

MySQL在使用不等于(!=或者<>),not in ,not exists 的时候无法使用索引会导致全表扫描。原理呢?


想象一下辅助索引树!

从辅助索引树中不能直接定位name!=lilei的节点,需要遍历所有节点才能知道。

值得注意的是:对于大于,小于,大于等于,小于等于这种条件,MySQL虽然可以走索引,但不一定真的走索引,如果可能出现大量回表,还不如遍历所有数据,所以有时候觉得应该走索引但没走,这是因为MySQL经过成本评估的结果,成本评估可能跟检索比例,表大小等诸多因素有关。比如下面的查询语句。

EXPLAIN SELECT * FROM employees WHERE name > 'LiLei' AND age = 22 AND position ='manager'; 

MySQL索引使用case的原理分析_聚簇索引_05

这个语句的where条件涵盖了联合索引的三个字段,但没有走索引,原因就是name是一个范围。查询要先找索引树根节点,而name就是索引树的入口,如果name不是确定的,可想而知,后面的age和position都是不确定的且无序,相当于整棵树都无法被利用。并且,如果数据量大,回表次数多,不如全表扫描,没必要走索引。


对于联合索引中第一个字段是范围的情况,一般不走索引,有一种情况例外,类似like 'xxx%',确切来说是索引下推情况下可以走索引


EXPLAIN SELECT * FROM employees WHERE name like '%Lei' AND age = 22 AND position ='manager'; 

MySQL索引使用case的原理分析_B+Tree_06

like语句带有左通配符不能走索引。 原理是什么?


想象一下辅助索引树!

如果索引字段的左侧字符不确定,那么就相当于无法直接确定哪些叶子节点能与之匹配,所以需要扫描全部叶子节点才能知道。

EXPLAIN SELECT * FROM employees WHERE name like 'Lei%' AND age = 22 AND position ='manager';

like语句带无左通配符可能走索引。

like 'Lei%感觉就是一个范围查找,根据上一条原则,联合索引的第一个字段如果是范围一般不走索引,这里应该不走索引呀,怎么也可以走索引呢?那原理是什么?这里要引出来一个MySQL的优化:索引下推

索引下推

索引下推是MySQL5.6及以上版本的优化方式,要了解索引下推,需要了解where条件过滤处理的情况。

where条件的过滤处理的情况

  • index key:用于确定SQL查询在索引中的连续范围(起始范围+结束范围,定值查询其实是起始范围和结束范围相等的特殊范围)。
  • index filter:在使用index key确定了扫描的连续范围后,在此范围内,还有一些不符和条件的可以使用索引来进行过滤,这就是index filter。
  • table filter:where语句中不能使用index filter进行过滤的,只能把table中的结果一行行进行条件过滤。

如何确定where条件走什么情况呢?

  • MySQL5.6以前版本,并不区分index filter和table filter,会将index key索引确定范围的记录全部回表,并读取完整记录,然后返回给MySQL Server层进行where条件过滤,相当于只有table filter。
  • MySQL5.6以上的版本,index filter和table filter区分开,index filter下降到InnoDB的索引层面行过滤,减少了回表的次数与减少了返回MySQL Server层的数据量和过滤过程,因此提高了SQL的执行效率。
  • 所以其实所谓索引下推,其实就是使用了index filter而已。只不过在MySQL架构(MySQL架构分为Server层和存储引擎层)中,将原本Server层做的事情“下推”到存储引擎层去做。笔者推测,除了效率的提升,这里面可能也有一些架构方面的原因:MySQL支持多种存储引擎,因此架构上设计就会将存储引擎与Server层分离,不同的存储引擎可以有自己不同的实现,但Server层提供统一接口和做一些统一的操作,所以我相信MySQL的开发者不应该想不到将where使用index filter来做,只是放在Server层更加统一规范,且早先数据库的数据量并没有现在这么庞大,性能上没有那么多苛刻的要求。但随着互联网时代数据量的急剧增加,性能要求高,且大多数场景下都是用InnoDB,所以才将where过滤下推到存储引擎层的索引上去做处理。

MySQL索引使用case的原理分析_B+Tree_07

回到刚刚的语句:SELECT * FROM employees WHERE name like 'Lei%' AND age = 22 AND position ='manager'; 


MySQL对like语句使用索引下推:在通过'Lei%'确定了扫描范围后,在这个范围内的所有叶子节点中都含有age和position字段,这时候直接进行age和position的条件过滤,找到满足条件的数据,在进行回表操作。所以,其实不管是like 'Lei%'、like 'Lei%xxx%'还是'Lei%xxx%qqq',只要前面字符确定,都有可能走索引。


当然like 'Lei%'可能走联合索引,也可能不走,这还是跟成本评估有关,如果本身数据量大,或者能够匹配的字符记录很多,很可能也不走索引,走全表扫描。

由于MySQL索引下推的优化让这条语句从效果上有点像WHERE name='Lei' AND age = 22 AND position ='manager',所以我们可以在效果上把name like 'Lei%'等同于name='Lei%'

小考题:

假设有联合索引index(a,b,c)

select * from table where a=3 and b like '%sss%' and c=4 走联合索引么?走索引的话有使用到a、b、c么?


答案:可能走联合索引,在走联合索引的时候只是用到了a,没有用到b和c,因为定位到a=3的节点后,a=3节点中,b虽然有序,但要匹配'%sss%',必须遍历所有a=3的节点才能找到满足b条件的,而对于c,b本身都需要遍历,c必然也不会直接可定位。

select * from table where a=3 and b like 'kk%sss%' and c=4 走联合索引么?走索引的话有使用到a、b、c么?


答案:可能走联合索引,a、b、c都有使用到,a肯定使用到,‘kk’字符是确定的,且在字符串最左边,由之前的理论,b也在索引中使用到,再由索引下推可以得出,b like 'kk%sss%'相当于b='kk',相当于定值查询(实际上是索引下推),因此后面的c的过滤也会在索引中使用到。

覆盖索引(索引覆盖)

EXPLAIN SELECT name,age FROM employees WHERE name= 'LiLei' AND age = 30 AND position='manager'; 

MySQL索引使用case的原理分析_聚簇索引_08

尽量使用覆盖索引方式。


覆盖索引不是一种索引,是使用索引的一种方式,比如这个语句,select后面的列在联合索引中被包含了,那么在辅助索引树中,直接查询到叶子节点后,就不需要回表了,效率会比较高,因此这里的“覆盖”不是把索引替换了的意思,是索引树中覆盖(包含)了你要查询的列。

MySQL索引使用case的原理分析_innodb引擎_09

当然上述语句很白痴,不会有人这么写,这里只是举个例子。但是你可能会有这种需求, SELECT name,posistion FROM employees WHERE name= 'LiLei' AND age >20,这也是可能使用到覆盖索引,相当于强制走了辅助索引。


小总结

这小节主要介绍了

  • 一些case的原理分析,主要是从索引数据结构角度去看为什么会有这样的原则。
  • 索引下推。虽然MySQL有索引下推机制,但是我们还是尽量少用like,效率并不是很高。
  • 索引覆盖,尽量能使用索引覆盖就使用索引覆盖,特别是范围查找,能减少大量回表操作。