最后需要说明的是,类似于order by,如果查询中使用到了distinct和group by等查询,通过本小节中最开始的表格可以看出,如果distinct和group by的是覆盖where条件后索引的最后一个字段,那么其也是可以使用到索引的。
2.3 前缀索引MySQL的前缀索引可以分为三类:联合索引前缀,like前缀和字符串前缀。这三种前缀索引我们都会讲到其用法,并且会介绍其使用过程中需要注意的问题。对于字符串前缀索引,作为对比,这里会详细讲解哈希索引的用法。
2.3.1 联合索引前缀联合索引前缀指的是对于联合索引(A, B, C),那么如果查询的等值条件是(A),(A, B)或(A, B, C),那么这几个查询都是可以用到该联合索引的,该原则也称为“最左前缀匹配原则”。如下图所示为一个联合索引的表示意图:
图中索引为(name, city, interest)三个字段联合的索引。从图中可以很明显的看出,如果查询条件为where;那么就只需要根据B+树定位到name字段第一个Bush所在的值,然后顺序扫描后续数据,直到找到第一个不为Bush的数据即可,扫描过程中将该索引片的数据id记录下来,最后根据id查询聚簇索引获取结果集。同理对于查询条件为where and city='Chicago';的查询,MySQL可以根据联合索引直接定位到中间灰色部分的索引片,然后获取该索引片的数据id,最后根据id查询聚簇索引获取结果集。
根据上图和对索引定位过程的分析可以得出联合索引前缀的两个注意点:
无法跨越字段使用联合索引,如where and interest='baseball';。从上图可以看出,对于该查询,name字段是可以使用联合索引的第一个字段过滤大部分数据的,但是对于interest字段,其无法通过B+树的特性直接定位第三个字段的索引片数据,比如这里的baseball就分散在了第二条和第七条数据之中。不过这里需要说明的是,interest字段其实进行的是覆盖索引扫描,后续将讨论覆盖索引扫描的用法。
对于非等值条件,如>、<、!=等,联合索引前缀对于索引片的过滤只能到第一个使用非等值条件的字段为止,后续字段虽然在联合索引上也无法参与索引片的过滤。这里比如where and city>'Chicago' and interest='baseball';,对于该查询条件,首先可以根据name字段过滤索引片中第一个字段的非Bush的数据,然后根据联合索引的第二个字段定位到索引片的Chicago位置,由于其是非等值条件,这里MySQL就会从定位的Chicago往下顺序扫描,由于interest字段是可能分散在索引第三个字段的任何位置的,因而第三个字段无法参与索引片的过滤(同上一注意点一样,这里interest是参与的覆盖扫描)。
SQL语句: select * from actor where first_name='rMqChueZJThP';
索引 无索引 first_name, last_name耗时 6.34 0.13
上述查询只使用到了first_name一个字段,但是表上建有(first_name, last_name)的联合索引,可以看到使用联合索引前缀的查询几乎和使用单列索引查询的效率一样高,如下是该查询的执行计划,可以看到没有联合索引时使用的是全表扫描,有联合索引时使用的是该联合索引:
2.3.2 like前缀对于like前缀,其是指在使用like查询时,如果使用的表达式为first_name like 'rMq%';那么其是可以用到first_name字段的索引的。但是对于first_name like '%Chu%';,其就无法使用first_name的索引。
这里需要说明的是,对于like前缀,MySQL底层实际上是使用了一个补全策略来使用索引的,比如这里first_name like 'rMq%';,MySQL会将其补全为两条数据:rMqAAAAA和rMqzzzzz,后面补全部分的长度为当前字段的最大长度。在使用索引查询时,MySQL就使用这两条数据进行索引定位,最后需要的结果集就是这两个定位点的中间部分的数据。如下是使用like前缀的一个示意图: