网上对覆盖索引的定义有如下三种:
个人认为,覆盖索引,通俗些说,就是能在某次查询中能同时覆盖到所有的查询结果和查询条件的索引。这里澄清下,覆盖索引首先是一个索引;覆盖索引覆盖的是什么呢?覆盖的是所有查要读取的所有的列,同时也覆盖所有的查询条件。
另外有一个争论的问题:一个索引是否是覆盖索引,是针对某次查询而言的?还是一个索引定义之后,它是否是覆盖索引也就确定了?针对这个问题,在几个群里讨论了之后,没有得出一个统一的结论,感觉每一种解释都不和先前网上的一些定义冲突,都能说的过去。本篇更偏向于第一种观点:一个索引是否是覆盖索引,它定义好之后是无法确定是否是覆盖索引的,只有具体到某次查询,才知道这个索引是否同时覆盖了查询结果的列和查询条件,从而做出是否是覆盖索引的判定,所以一个索引是否是覆盖索引是针对某次查询而言的。
在Explain的时候,输出的Extra信息中如果有“Using Index”,就表示这条查询使用了覆盖索引。
使用了覆盖索引的查询也称为索引覆盖查询。
覆盖索引是非常有用的工具,能够极大的提高性能。如果SQL查询只需要扫描索引而无需回表,会带来多少好处:
不是所有类型的索引都可以成为覆盖索引。覆盖索引必须要存储索引的列值(因为SQL查询结果和查询条件的都涉及到具体的值,而覆盖索引需要覆盖查询结果和查询条件),而哈希索引、空间索引和全文索引等都不存储索引列的值,所以MySQL只能使用B-Tree或B+Tree索引做覆盖索引。另外,不同的存储引擎实现覆盖索引的方式也不同,而且不是所有的引擎都支持覆盖索引。
有这么一张表:
CREATE TABLE `user_group` (
`id` int(11) NOT NULL auto_increment,
`uid` int(11) NOT NULL,
`group_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `uid` (`uid`),
KEY `group_id` (`group_id`)
) ENGINE=InnoDB AUTO_INCREMENT=750366 DEFAULT CHARSET=utf8
看AUTO_INCREMENT就知道数据并不多,75万条。然后是一条简单的查询:
SELECT SQL_NO_CACHE uid FROM user_group WHERE group_id = 245;
该条语句查询需要0.15s左右。但是真实的业务需求比这个复杂,查询需要2.2s.,最终定位到问题症结是在这条SQL。
Explain的结果是:
+----+-------------+------------+------+---------------+----------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+------+---------------+----------+---------+-------+------+-------+
| 1 | SIMPLE | user_group | ref | group_id | group_id | 4 | const | 5544 | |
+----+-------------+------------+------+---------------+----------+---------+-------+------+-------+
看上去已经用上索引了,好像已经没有办法优化了。实际上不然,当我们再创建一个如下联合索引时(注意,创建该索引后要把原来的group_id索引删除,因为按照定义的先后顺序,where条件会优先使用group_id索引而不是联系索引查询):
ALTER TABLE user_group ADD INDEX group_id_uid (group_id, uid);
然后,不可思议的事情发生了……这句SQL查询的性能发生了巨大的提升,居然已经可以跑到0.00s左右了。经过优化的SQL再结合真实的业务需求,也从之前2.2s下降到0.05s。
再Explain一次:
+----+-------------+------------+------+-----------------------+--------------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+------+-----------------------+--------------+---------+-------+------+-------------+
| 1 | SIMPLE | user_group | ref | group_id,group_id_uid | group_id_uid | 4 | const | 5378 | Using index |
+----+-------------+------------+------+-----------------------+--------------+---------+-------+------+-------------+
可以看到使用了覆盖索引,MySQL只需要通过索引就可以返回查询所需要的数据,而不必在查到索引之后再去查询数据。
1,当只查询表的id字段时,是索引覆盖查询,如下为索引覆盖查询
SELECT SQL_NO_CACHE id FROM user_group;
2,当where和select查询的列分别创建单独的索引时,不是索引覆盖查询
如id为主键,为uid和group_id分别创建索引时,则如下不是索引覆盖查询:
SELECT SQL_NO_CACHE uid FROM user_group WHERE group_id = 123;
因为该查询是通过group_id索引获取到主键值,通过主键在数据表中获取数据(回表查询),再查询其中uid的值
3,当无where时,select查询的列为主键和另一个索引时,是索引覆盖查询
如id为主键,uid为索引,则如下查询是索引覆盖查询(其中id和uid顺序可以改变):
SELECT SQL_NO_CACHE id, uid FROM user_group;
因为通过uid索引,可以直接获取到uid和id的值,而无需回表查询。
当如上查询有where时,则where条件必须是另一个非主键索引,才是索引覆盖查询
4,select查询的列为两个及两个以上单独加了索引的字段时(或包含非索引字段),不是索引覆盖查询
如id为主键,分别为uid和group_id创建索引,如下查询不是索引覆盖查询
SELECT SQL_NO_CACHE uid,group_id FROM user_group;
因为此时不会使用到索引,而是会做全表查询。
而explain SELECT SQL_NO_CACHE uid,group_id FROM user_group where uid = 2 and group_id = 123;会同时使用两个索引,但也不是索引覆盖查询。
5,如果select查询的列只要都同时属于同一个组合索引中的全部或部分列时,都是索引覆盖查询
5.1 如id为主键,存在组合索引group_id_uid(group_id, uid)时,如下查询都是索引覆盖查询:
SELECT SQL_NO_CACHE group_id FROM user_group;
SELECT SQL_NO_CACHE uid FROM user_group;
SELECT SQL_NO_CACHE uid,group_id FROM user_group;
原因:MySQL查询优化器会在执行查询前判断是否有一个索引能进行覆盖,如果存在的话,则会使用该索引进行查询。
5.2 当上述带上where条件查询时,如果where后面要是包含除了该联合索引以外的字段(或不符合索引的前缀匹配原则)时,都不是索引覆盖查询(联合索引和主键同时作为查询条件也不行)。
5.3 如果where后面的查询条件都为联合索引中的字段且是符合前缀匹配原则时,是索引覆盖查询,如下是索引覆盖查询:
SELECT SQL_NO_CACHE id, group_id FROM user_group where uid = 2 and group_id = 123
6, where后面的查询条件包含非索引字段 或两个及两个以上的所属不同索引字段 时,都不是索引覆盖查询。
6.1 如group_id为联合索引的前缀,name为一个单独的索引,则如下不是索引覆盖查询
SELECT SQL_NO_CACHE group_id, name FROM user_group where group_id = 123 and name = "test" ;
6.2 分别为uid和group_id创建独立索引, 即使只查询其中一个索引字段的值或id的值,也不是索引覆盖查询。
SELECT SQL_NO_CACHE uid FROM user_group where uid = 2 and group_id = 123;
7,where条件中包含like的操作可能也是索引覆盖查询
如果id为主键,name为索引
7.1 执行如下,关键词前缀匹配查询,只范围查询具有对应前缀的数据值
explain SELECT name FROM `user_group` where name like "test%";
显示结果为 Using where; Using index:
+----+-------------+------------+------------+-------+---------------+------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+---------------+------+---------+------+------+----------+--------------------------+
| 1 | SIMPLE | user_group | NULL | range | name | name | 768 | NULL | 1 | 100.00 | Using where; Using index |
+----+-------------+------------+------------+-------+---------------+------+---------+------+------+----------+--------------------------+
7.2 执行如下,关键词非前缀匹配查询,查询所有的name索引值
explain SELECT name FROM `user_group` where name like "%test";
# 或
explain SELECT name FROM `user_group` where name like "%test%";
显示结果为 Using where; Using index:
+----+-------------+------------+------------+-------+---------------+------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+---------------+------+---------+------+------+----------+--------------------------+
| 1 | SIMPLE | user_group | NULL | index | NULL | name | 768 | NULL | 3 | 33.33 | Using where; Using index |
+----+-------------+------------+------------+-------+---------------+------+---------+------+------+----------+--------------------------+
则以上都是索引覆盖查询,但是无需回表查询。只是如果查询的列中包含其它非索引的列,可能需要回表查询,则不是索引覆盖查询。
注:以上都是Mysql 5.7.14版本:Server version: 5.7.14 MySQL Community Server (GPL)
参考:
https://www.cnblogs.com/happyflyingpig/p/7662881.html
https://www.cnblogs.com/zl0372/articles/mysql_32.html