什么是覆盖索引?
本文为笔者近来学习的笔记,在解释覆盖索引之前势必简单回顾一下索引基本知识?
索引
索引是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。通常类比为图书目录。
聚集索引与非聚集索引
聚集索引:
聚集索引中键值的逻辑顺序决定了表中相应行的物理顺序,例如电话本,索引为(姓,名),数据值为电话号,在一个表中通常只有一个聚集索引, 聚集索引对于那些经常要搜索范围值的列特别有效。使用聚集索引找到包含第一个值的行后,便可以确保包含后续索引值的行在物理相邻。
例如,如果应用程序执行 的一个查询经常检索某一日期范围内的记录,则使用聚集索引可以迅速找到包含开始日期的行,然后检索表中所有相邻的行,直到到达结束日期。这样有助于提高此类查询的性能。同样,如果对从表中检索的数据进行排序时经常要用到某一列,则可以将该表在该列上聚集(物理排序),避免每次查询该列时都进行排序,从而节 省成本。
当索引值唯一时,使用聚集索引查找特定的行也很有效率。
非聚集索引
一种索引,该索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同。我们可以这么理解聚簇索引:索引的叶节点就是数据节点。而非聚簇索引的叶节点仍然是索引节点(例如存储数据存放的地址,而非数据),有一个指针指向对应的数据块。
我们的汉语字典的正文本身就是一个聚集索引。比如,我们要查“安”字,就会很自然地翻开字典的前几页,因为“安”的拼音是“an”,而按照拼音排序汉字的字典是以英文字母“a”开头并以“z”结尾的,那么“安”字就自然地排在字典的前部。如果您翻完了所有以“a”开头的部分仍然找不到这个字,那么就说明您的字典中没有这个字;同样的,如果查“张”字,那您也会将您的字典翻到最后部分,因为“张”的拼音是“zhang”。也就是说,字典的正文部分本身就是一个目录,您不需要再去查其他目录来找到您需要找的内容。我们把这种正文内容本身就是一种按照一定规则排列的目录称为“聚集索引”。
如果您认识某个字,您可以快速地从自动中查到这个字。但您也可能会遇到您不认识的字,不知道它的发音,这时候,您就不能按照刚才的方法找到您要查的字,而需要去根据“偏旁部首”查到您要找的字,然后根据这个字后的页码直接翻到某页来找到您要找的字。但您结合“部首目录”和“检字表”而查到的字的排序并不是真正的正文的排序方法,比如您查“张”字,我们可以看到在查部首之后的检字表中“张”的页码是672页,检字表中“张”的上面是“驰”字,但页码却是63页,“张”的下面是“弩”字,页面是390页。很显然,这些字并不是真正的分别位于“张”字的上下方,现在您看到的连续的“驰、张、弩”三字实际上就是他们在非聚集索引中的排序,是字典正文中的字在非聚集索引中的映射。我们可以通过这种方式来找到您所需要的字,但它需要两个过程,先找到目录中的结果,然后再翻到您所需要的页码。我们把这种目录纯粹是目录,正文纯粹是正文的排序方式称为“非聚集索引”。
索引分类
主键索引(PRIMARY),以主键列作为索引,最常用的索引。
普通索引(INDEX):以普通列作为索引,唯一任务是加快对数据的访问速度,因此,应该只为那些最经常出现在查询 条件(WHERE column=)或者排序条件(ORDERBY column)中的数据列创建索引。
唯一索引(UNIQUE):以唯一值作为索引,目的往往不是为了提高访问速度,而只是为了避免数据出现重复。
组合索引(Composite):多列组合起来作为索引,例如前文中提到的电话簿,就是以姓+名的形式作为索引
覆盖索引前因:回表
InnoDB引用的是B+树索引模型,前文中对索引的种类划分为两大类:主键(聚集)索引和非聚集索引,那么问题就在于比较两种索引的区别了,我们这里建立一张学生表,其中包含字段id设置主键索引、name设置普通索引、age(无处理),并向数据库中插入4条数据:("小赵", 10)("小王", 11)("小李", 12)("小陈", 13)。
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`name` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '名称',
`age` int(3) unsigned NOT NULL DEFAULT '1' COMMENT '年龄',
PRIMARY KEY (`id`),
KEY `I_name` (`name`)
) ENGINE=InnoDB;
INSERT INTO student (name, age) VALUES("小赵", 10),("小王", 11),("小李", 12),("小陈", 13);
此时表中的数据:
mysql> select * from student;
+----+------+-----+
| id | name | age |
+----+------+-----+
| 1 | 小赵 | 10 |
| 2 | 小王 | 11 |
| 3 | 小李 | 12 |
| 4 | 小陈 | 13 |
+----+------+-----+
4 rows in set (0.00 sec)
每一个索引在 InnoDB 里面对应一棵B+树,那么此时就存着两棵B+树:
可以发现区别在与叶子节点中,主键索引存储了整行数据,而非主键索引中存储的值为主键id
当我们执行以下语句:
select age from student where name = '小李';
执行顺序:
- 在name索引树上找到名称为小李的节点 id为03
- 从id索引树上找到id为03的节点 获取所有数据
- 从数据中获取字段命为age的值12,返回.
这样从非主键索引树搜索再回到主键索引树搜索的过程称为:回表
回表一定程度上消耗性能,那么如何降低这种性能损耗呢?于是提出了一种方法:覆盖索引.
覆盖索引
覆盖索引(covering index ,或称为索引覆盖)即从非主键索引中就能查到的记录,而不需要查询主键索引中的记录,避免回表的产生减少了树的搜索次数,显著提升性能。
覆盖索引的使用
如果一个业务中,很多类似于根据姓名查找年龄的业务,那么可以将这些热点业务重新根据(name , age)建立联合索引,先删除之前以name构建的索引:
ALTER TABLE student DROP INDEX I_name;
ALTER TABLE student ADD INDEX I_name_age(name, age);
联合索引:
再次执行如下sql:
select age from student where name = '小李';
执行流程:
- 在name,age联合索引树上找到名称为小李的节点。
- 此时节点索引里包含信息age直接返回 12,从而避免回表。
如何确定使用的是覆盖索引还是主键索引呢?
当发起一个索引覆盖查询时,在explain的extra列可以看到using index的信息: