如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为“覆盖索引”
覆盖索引的好处
覆盖索引是非常有用的工具,能够极大的提高性能。如果查询只需要查询索引,而无需回表,那将会带来很多好处:
- 索引条目通常远小于数据行大小,所以如果只需要读取索引,那么MySQL就会极大地减少数据访问量。这对缓存的负载非常重要,因为这种情况下响应时间大部分花费在数据拷贝上。覆盖索引对于I/O密集型的应用也有帮助,因为索引比数据更小,更容易全部放入内存中
- 因为索引是按照列值顺序存储的,所以对于I/O密集型的范围查询会比随即从磁盘读取每一行数据的I/O要少得多。
- 一些存储引擎如MyISAM在内存中只缓存索引,数据则依赖于操作系统来缓存,因此访问数据需要一次系统调用。这可能会导致严重的性能问题
- 由于InnoDB的聚簇索引,覆盖索引对InnoDB表特别游泳。InnoDB的二级索引在叶子节点中保存了行的主键,所以如果二级主键能够覆盖查询,则可以避免对主键索引的二次查询
不是所有类型的索引都可以成为覆盖索引。覆盖索引必须要存储索引列的值,而哈希索引、空间索引和全文索引等都不存储索引列的值,所以MySQL只能使用B-Tree索引做覆盖索引。
具体分析
当发起一个被索引覆盖的查询(也叫做索引覆盖查询)时,在EXPLAIN的Extra列可以看到“Using index”的信息。
例如,表inventory有一个多列索引(store_id,film_id)。如果只访问这两列,就可以使用这个索引做覆盖索引: EXPLAIN SELECT store_id,film_id FROM inventory;
可以看到Extra的值是“User index”
但是如果是下面这种查询语句,那么就不会使用覆盖索引了: EXPLAIN SELECT * FROM inventory WHERE actor='sean carrey' AND title like '%apollo'
其Extra的值是“Using where”。
这里索引无法覆盖该查询,有两个原因:
- 没有任何索引能过覆盖这个查询。因为查询从表中选择了所有的列,而没有任何索引覆盖了所有的列。不过,可以这样做:WHERE条件中的列是有索引可以覆盖的,因此MySQL可以使用该索引找到对应的actor并检查title是否匹配,过滤之后再读取需要的数据行。
- MySQL不能在索引中执行LIKe操作。MYSQL只允许在索引中做简单比较(等于、不等于以及大于)。MySQL能在索引中做最左前缀匹配的like比较(也就是%号在右边,比如 like ‘jhon%’),因为该操作可以转换为简单的比较操作。
解决上面的问题,就需要重写查询以及重新设计索引。先将索引扩展至覆盖三个数据列(artist,title,prod_id):
把这种方式叫做 延迟关联(defeered join),因为延迟了队列的访问(意思就是说先不取出所有列的值)。在查询的第一阶段MySQL可以使用覆盖索引,在FROM子句的自查询中找到匹配的prod_id,然后根据这些prod_id值在外层查询匹配获取需要的所有列值。
最后
因为InnoDB的二级索引的叶子节点包含了主键的值,这意味着InnoDB的二级索引可以有效地利用这些“额外”的主键列来覆盖查询。
例如,actor使用InnoDB存储引擎,并在last_name字段有二级索引,虽然该索引的列不包括主键actor_id,但也能够用于对actor_id做覆盖查询