首先捋清一下各种索引关系
接着说,对于Mysql的InnoDB 存储引擎来说,在绝大多数情况下使用 B+ 树建立索引,这是关系型数据库中查找最为常用和有效的索引,但是 B+ 树索引并不能找到一个给定键对应的具体值,它只能找到数据行对应的页,数据库把整个页读入到内存中,并在内存中查找具体的数据行。
而数据库中的 B+ 树索引可以分为聚集索引(clustered index)和辅助索引(secondary index),它们之间的最大区别就是,聚集索引中存放着一条行记录的全部信息,而辅助索引中只包含索引列和一个用于查找对应行记录的『书签』,在 InnoDB 中这个书签就是当前记录的主键。一个表只有一个聚集索引,但是可以创建多个辅助索引来提升性能。
聚集索引具有非常明显的优势,因为数据的物理存储是通过主键来存储的,通过主键的数据查找是非常快速的;辅助索引在查找的时候一般需要经历两次查询,第一次通过辅助索引先找到对应的主键,接着通过聚集索引查找到对应的行记录,详细知识建议学习这篇文章『浅入浅出』MySQL 和 InnoDB。
对于InnoDB来说,默认创建的主键就是聚集索引,如果没有主键被定义,那么该表的第一个唯一非空索引被作为聚集索引,如果没有主键也没有合适的唯一索引,那么InnoDB内部会生成一个隐藏的主键作为聚集索引,这个隐藏的主键是一个6个字节的列,改列的值会随着数据的插入自增。
以上是两种不同的分类体系,比如聚集索引就是单列索引,如果是创建单列的辅助索引那么就是单列索引,如果创建多列索引,那么就是辅助索引,也可以叫做是组合索引。
组合索引和单列索引相比是对多个列创建索引。组合索引本质上也是一颗B+树,只不过它的键值是大于等于2的,好比(a,b)的值有(1,2)、(2,4)、(2,1)、(1,3)、(2,3),那么它也是按照顺序进行存放的:(1,2)、(1,3)、(2,1)、(2,3)、(2,4),例如对于SELECT * FROM TABLE a=xxx AND b=xxx,这个明显是可以使用(a,b)这个组合索引,而且对于SELECT * FROM TABLE a=xxx ,但是对于SELECT * FROM TABLE b=xxx就不能使用(a,b)的组合索引了,因为可以看到,对于b本来说,2,3,1,3,4并不是排序的。这也就是组合索引的最左前缀问题,大哥不能死,中间不能断等等,可以详细学习一下这篇文章,我就不展开叙述了MySQL高级知识(七)——索引面试题分析。下面举个例子来说明情况:首先创建表接着创建单列索引和组合索引:
CREATE TABLE student (
id INT NOT NULL,
age INT, NAME VARCHAR ( 50 )
) ENGINE = INNODB;
insert into student VALUES (1,12,'xiaoming');
insert into student VALUES (3,13,'huahua');
insert into student VALUES (1,11,'jianjian');
insert into student VALUES (2,15,'huihui');
insert into student VALUES (1,10,'linlin');
alter table student add key(id);
alter table student add key(id,age);
show index from student;
可以看到查询的结果为我们分别创建了单列索引id和组合索引(id,age)接着我们查询一下,可以看到可以使用的possible_keys有id,id_2两种,但是优化器执行的是id,也就是单列索引,这是因为相对于组合索引来说,单列索引它的B+树的叶子结点只包含单个键值,所以理论上来说一个页可以存放的记录应该更多。
接着我们继续进行实验:
这次优化器选择了id_2,也就是组合索引,这是因为order by必须进行排序,而对于组合索引id_2来说在创建的时候已经进行过排序了,所以优化器选择了使用id_2组合索引,当然id的单列索引也是可以使用的,下面我们强制使用索引id,可以看到出现了Using temporary; Using filesort,这个说明MySQL进行了一次额外排序,这样性能会下降很多,而直接使用就组合索引id_2就不会出现这种情况了。
对于组合索引还是建议好好看这篇文章:MySQL高级知识(七)——索引面试题分析,实验的非常详细。
InnoDB支持覆盖索引,意思就是能从辅助索引中就可以得到查询记录,而不需要查询聚集索引中的记录。使用覆盖索引的一个好处是辅助索引不包含整行记录信息,所以大小远远小于聚集索引,这样可以减少大量IO操作。覆盖索引的另外一个好处是对于一些统计问题而言,接着我们继续看例子:
可以看到,possible_keys为Null,但是实际执行的时候优化器却选择了辅助的单列索引id,而Extra的Using index就代表优化器进行了覆盖索引操作。
除此之外,对于(a,b)的联合索引,在通常情况下,只根据b进行查询的时候,一般情况下是不能使用组合索引的,这是就是在前文中说的那个带头大哥不能死。但是如果是统计操作,并且是索引覆盖的,那么优化器会进行优化。
可以看到,possible_keys仍然为Null,但是实际执行的时候优化器却选择了辅助的组合索引id_2,即(id,age)的组合索引,而Extra的Using index就代表优化器进行了覆盖索引操作。
在有些情况下,使用explain来查看sql执行的时候会发现优化器并没有使用辅助索引,而采用了全表扫描的方式,也就是使用聚集索引来获取结果,这种情况多发生于范围查找、JOIN链接操作等情况。例如这是我的表:主键索引也就是聚集索引是id,辅助索引有(id)id_index和(id,empno)id_empno_index共计3个索引。
接着我们对主键id进行范围查找
可以看到,possible_keys三个索引都可以使用,但是优化器选择了primary聚集索引,也就是全表扫描,而不是辅助索引id_index,这是为什么呢?这是因为id_index辅助索引不能覆盖我们所要查询的所有信息,因为这里我们查询的是*,也就是行的所有信息,如果要使用id_index的辅助索引,首先查出id,接着在使用id使用聚集索引查找所对应的行的所有信息,id是顺序存放的,但是再次通过id查找的数据则是无序的,所以优化器就选择了聚集索引,可以一次性获得所有数据。接着我们同样的例子尝试一下只查找id
可以看出虽然仍然是范围查找,但是只查找了id,所以优化器选择了id_index的辅助索引,也就是覆盖索引,从Using index可以看出。
参考资料:
《MySQL技术内幕 InnoDB存储引擎》
『浅入浅出』MySQL 和 InnoDB
MySQL高级知识(七)——索引面试题分析