看前提问:table中有多个字段组成的联合索引(a,b,c),查询时哪些情况能够命中索引呢?
话不多说,直接开搞:
数据库表结构如下:
1 CREATE TABLE `test` ( 2 `id` bigint(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', 3 `class_id` int(4) NOT NULL DEFAULT '0' COMMENT '班级ID', 4 `score` int(4) NOT NULL DEFAULT '0' COMMENT '学分', 5 `total` int(4) NOT NULL DEFAULT '0' COMMENT '总计', 6 PRIMARY KEY (`id`), 7 KEY `idx_class_score_total` (`class_id`,`score`,`total`) COMMENT '测试表' 8 ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COMMENT='测试表';
可以看到:表中根据class,score,total字段建立联合索引 idx_class_score_total ,下面开始验证我们的猜想;
首先我们验证当根据一个字段查询数据的情况:
1 EXPLAIN SELECT * FROM test WHERE class = 1;
2 EXPLAIN SELECT * FROM test WHERE score = 1;
3 EXPLAIN SELECT * FROM test WHERE total = 1;

上述结果发现:type有两种类型:ref和index;
index:这种类型表示mysql会对整个该索引进行扫描。要想用到这种类型的索引,对这个索引并无特别要求,只要是索引,或者某个联合索引的一部分,mysql都可能会采用index类型的方式扫描。但是呢,缺点是效率不高,mysql会从索引中的第一个数据一个个的查找到最后一个数据,直到找到符合判断条件的某个索引。所以,上述语句会触发索引。
ref:这种类型表示mysql会根据特定的算法快速查找到某个符合条件的索引,而不是会对索引中每一个数据都进行一一的扫描判断,也就是所谓你平常理解的使用索引查询会更快的取出数据。而要想实现这种查找,索引却是有要求的,要实现这种能快速查找的算法,索引就要满足特定的数据结构。简单说,也就是索引字段的数据必须是有序的,才能实现这种类型的查找,才能利用到索引。
结果:根据一个字段查询的时候实际上都可以命中索引,只不过ref效率更高,index情况效率不高;
其次我们再来验证根据任意两个字段查询的结果:
1 EXPLAIN SELECT * FROM test WHERE class = 1 AND score = 1;
2 EXPLAIN SELECT * FROM test WHERE class = 1 AND total = 1;
3 EXPLAIN SELECT * FROM test WHERE score = 1 AND total = 1;

上述实践可得:a_b,a_c可以命中索引,b_c没有命中索引;
是不是有个问题?为什么a_c也可以命中索引?
我们仔细观察下sql1和sql2的执行结果,可以发现key_len是不一样的且ref不一样,a_c命中索引并不是全命中,只是部分命中索引,命中的是a的索引,所以key_len=4且ref=const;
最后我们看下根据三个字段一起查询的结果(包括打乱顺序);
1 EXPLAIN SELECT * FROM test WHERE class = 1 AND score = 1 AND total = 1;
2 EXPLAIN SELECT * FROM test WHERE total = 1 AND score = 1 AND class = 1;

上述结果发现:两条sql的执行结果是一致的,所以当根据这三个字段查询数据时都可以命中索引无论顺序;
是不是又有一个疑问?为什么打乱顺序也可以命中索引?
mysql中的查询优化器会纠正这条sql语句该以哪种顺序执行效率最高,最后生成真正的执行计划(QEP),尽量利用到索引时查询顺序效率最高。所以mysql查询优化器最终会以(a_b_c)这种顺序查询,所以b_a_c可以命中索引;
附录:
各属性含义:
-
id: 查询的序列号
-
select_type: 查询的类型,主要是区别普通查询和联合查询、子查询之类的复杂查询
- SIMPLE:查询中不包含子查询或者UNION
- 查询中若包含任何复杂的子部分,最外层查询则被标记为:PRIMARY
- 在SELECT或WHERE列表中包含了子查询,该子查询被标记为:SUBQUERY
-
table: 输出的行所引用的表
-
type: 访问类型
- ALL: 扫描全表
- index: 扫描全部索引树
- range: 扫描部分索引,索引范围扫描,对索引的扫描开始于某一点,返回匹配值域的行,常见于between、<、>等的查询
- ref: 使用非唯一索引或非唯一索引前缀进行的查找
- (eq_ref和const的区别:)
- eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描
- const, system: 单表中最多有一个匹配行,查询起来非常迅速,例如根据主键或唯一索引查询。system是const类型的特例,当查询- 的表只有一行的情况下, 使用system。
- NULL: 不用访问表或者索引,直接就能得到结果,如select 1 from test where 1
-
key: 显示MySQL实际决定使用的索引。如果没有索引被选择,是NULL
-
key_len: 使用到索引字段的长度
注:key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的。 -
ref: 显示哪个字段或常数与key一起被使用
-
rows: 这个数表示mysql要遍历多少数据才能找到,表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数,在innodb上可能是不准确的
-
Extra: 执行情况的说明和描述。包含不适合在其他列中显示但十分重要的额外信息。
-
Using index:表示使用索引,如果只有 Using index,说明他没有查询到数据表,只用索引表就完成了这个查询,这个叫覆盖索引。
-
Using where:表示条件查询,如果不读取表的所有数据,或不是仅仅通过索引就可以获取所有需要的数据,则会出现 Using where。