聚集索引
InnoDB 中使用了聚集索引,就是将表的主键用来构造一棵 B+ 树,并且将整张表的行记录数据存放在该 B+ 树的叶子节点中。也就是所谓的索引即数据,数据即索引。
由于聚集索引是利用表的主键构建的,所以每张表只能拥有一个聚集索引。聚集索引的叶子节点就是数据页。换句话说,数据页上存放的是完整的每行记录。
因此聚集索引的一个优点就是:通过过聚集索引能获取完整的整行数据。另一个优点是:对于主键的排序查找和范围查找速度非常快。 如果我们没有定义主键呢?MySQL会使用唯一性索引,没有唯一性索引,MySQL 也会创建一个隐含列 RowID 来做主键,然后用这个主键来建立聚集索引。
辅助索引
聚簇索引只能在搜索条件是主键值时才能发挥作用,因为B+树中的数据都是按照主键进行排序的,那如果我们想以别的列作为搜索条件怎么办?我们一般会建立多个索引,这些索引被称为辅助索引/二级索引。
对于辅助索引(Secondary Index,也称二级索引、非聚集索引),叶子节点并不包 含行记录的全部数据。叶子节点除了包含键值以外,每个叶子节点中的索引行中还 包含了相应行数据的聚集索引键。
辅助索引的存在并不影响数据在聚集索引中的组织,因此每张表上可以有多个辅助索引。
当通过辅助索引来寻找数据时,InnoDB 存储引擎会遍历辅助索引并通过叶级别的指针获得指向主键索引的主键,然后再通过主键索引(聚集索引)来找到一个完整的行记录,这个过程也被称为回表。
也就是根据辅助索引的值查询一条完整的用户记录需要使用到2棵B+树----一次辅助索引,一次聚集索引。
为什么我们还需要一次回表操作呢?直接把完整的用户记录放到辅助索引的叶子节点不就好了么?如果把完整的用户记录放到叶子节点是可以不用回表,但是太占地方了,相当于每建立一棵B+树都需要把所有的用户记录再都拷贝一遍,这就有点太浪费存储空间了。而且每次对数据的变化要在所有包含数据的索引中全部都修改一次,性能也非常低下。
很明显,回表的记录越少,性能提升就越高,需要回表的记录越多,使用二级索引的性能就越低,甚至让某些查询宁愿使用全表扫描也不使用二级索引。
那什么时候采用全表扫描的方式,什么时候使用采用二级索引 + 回表的方式去执行查询呢?这个就是查询优化器做的工作,查询优化器会事先对表中的记录计算一些统计数据,然后再利用这些统计数据根据查询的条件来计算一下需要回表的记录数,需要回表的记录数越多,就越倾向于使用全表扫描,反之倾向于使用二级索引 + 回表的方式。
MRR
每次从二级索引中读取到一条记录后,就会根据该记录的主键值执行回表操作。而在某个扫描区间中的二级索引记录的主键值是无序的,也就是说这些二级索引记录对应的聚簇索引记录所在的页面的页号是无序的。
每次执行回表操作时都相当于要随机读取一个聚簇索引页面,而这些随机IO带来的性能开销比较大。MySQL中提出了一个名为 Disk-Sweep Multi-Range Read (MRR,多范围读取)
的优化措施,即先读取一部分二级索引记录,将它们的主键值排好序之后再统一执行回表操作。
相对于每读取一条二级索引记录就立即执行回表操作,这样会节省一些 IO 开销。使用这个MRR优化措施的条件比较苛刻,所以我们直接认为每读取一条二级索引记录就立即执MRR行回表操作。MRR的详细信息,可以查询官方文档。
联合索引/复合索引
将表上的多个列组合起来进行索引我们称之为联合索引或者复合索引,比如 index(a,b)
就是将 a,b
两个列组合起来构成一个索引。
自适应哈希索引
InnoDB 存储引擎除了我们前面所说的各种索引,还有一种自适应哈希索引。
我们知道B+树的查找次数,取决于B+树的高度,在生产环境中,B+树的高度一般为34层,故需要34次的IO查询。
所以在InnoDB存储引擎内部自己去监控索引表,如果监控到某个索引经常用,那么就认为是热数据,然后内部自己创建一个hash索引,称之为自适应哈希索引(Adaptive Hash Index, AHI)。
创建以后,如果下次又查询到这个索引,那么直接通过hash算法推导出记录的地址,直接一次就能查到数据,比重复去B+tree 索引中查询三四次节点的效率高了不少。InnoDB存储引擎使用的哈希函数采用除法散列方式,其冲突机制采用链表方式。
注意,对于自适应哈希索引仅是数据库自身创建并使用的,我们并不能对其进行干预。
覆盖索引
覆盖索引也是我们经常见到的名词,InnoDB存储引擎支持覆盖索引(covering index,或称索引覆盖),即从辅助索引中就可以得到查询的记录,而不需要查询聚集索引中的记录。使用覆盖索引的一个好处是辅助索引不包含整行记录的所有信 息,故其大小要远小于聚集索引,因此可以减少大量的IO操作。
覆盖索引可以视为索引优化的一种方式,而并不是索引类型的一种。
三星索引
对于一个查询而言,一个三星索引,可能是其最好的索引。
如果查询使用三星索引,一次查询通常只需要进行一次磁盘随机读以及一次窄索引片的扫描,因此其相应时间通常比使用一个普通索引的响应时间少几个数量级。
三星索引概念是在《Rrelational Database Index Design and the optimizers》一书 (这本书也是《高性能MySQL》作者强烈推荐的一本书)中提出来的。原文如下:
The index earns one star if it places relevant rows adjacent to each other, a second star if its rows are sorted in the order the query needs, and a final star if it contains all the columns needed for the query.
- 索引将相关的记录放到一起则获得一星;
- 如果索引中的数据顺序和查找中的排列顺序一致则获得二星;
- 如果索引中的列包含了查询中需要的全部列则获得三星。
一星:它的意思是如果一个查询相关的索引行是相邻的或者至少距离足够靠近的话,必须扫描的索引片宽度就会缩至最短。即让索引片尽量变窄,也就是我们所说的索引的扫描范围越小越好。 二星(排序星):在满足一星的情况下,当查询需要排序,group by、order by,查询所需的顺序与索引是一致的(索引本身是有序的),就可以不用再另外排序了。一般来说,排序是影响性能的关键因素。 三星(宽索引星):满足二星的情况下,如果索引中包含了这个查询所需要的所有列(包括where子句和select子句中所需的列,也就是覆盖索引),这样以来,查询就不再需要徽标减少了查询的步骤和IO请求次数,性能几乎可以提升一倍。
这三个星,哪个最重要?第三颗星。因为将一个列排除在索引只要可能会导致很多磁盘随机读(回表操作)。 可以理解为第三颗星比重50%,第一颗星为27%,第二颗星为23%,所以大部分情况下,会考虑第一颗星,根据业务情况调整这两颗星的优先度。