聚簇索引
InnoDB的聚簇索引实际上在同一个结构中保存了B-Tree索引和数据行。当表有聚簇索引时,它的数据行实际上存放在索引的叶子页(leaf page)中。Inno DB只能通过主键实现聚簇索引(不能像其它db那样选择某个索引作为聚簇索引)。
聚簇索引的优点:
- 可以把相关数据保存在一起。
- 数据访问更快。
- 使用覆盖索引扫描的查询可以直接使用页节点中的主键值。
聚簇索引的缺点:
- 插入速度严重依赖于插入顺序。按照主键的顺序插入是加载数据到InnoDB表中速度最快的方式。
- 更新聚簇索引列的代价很高,因为会强制InnoDB将每个被更新的行移动到新的位置。
- 基于聚簇索引的表在插入新行,或者主键被更新导致需要移动行的时候,可能面临“页分裂(page split)”的问题。
- 二级索引(非聚簇索引)可能比想象的要更大,因为在二级索引的叶子节点包含了引用行的主键列。
- 二级索引访问需要两次索引查找,而不是一次。
二级索引叶子节点保存的不是指向行的物理位置的指针,而是行的主键值。
这意味着通过二级索引查找行,存储引擎需要找到二级索引的叶子节点获得对应的主键值,然后根据这个值去聚簇索引中查找到对应的行。这里做了重复的工作:两次B-Tree查找而不是一次。(回表查询)
最好避免随机的(不连续且值的分布范围非常大)聚簇索引。
从性能的角度考虑,使用UUID来作为聚簇索引则会很糟糕:它使得聚簇索引的插入变得完全随机。
覆盖索引
MySQL也可以使用索引来直接获取列的数据,这样就不再需要读取数据行。如果索引的叶子节点中已经包含要查询的数据,那么还有什么必要再回表查询呢?如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为“覆盖索引”。
覆盖索引好处:
索引条目通常远小于数据行大小。
因为索引是按照列值顺序存储的(至少在单个页内是如此),所以对于I/O密集型的范围查询会比随机从磁盘读取每一行数据的I/O要少得多。
二级索引在叶子节点中保存了行的主键值,所以如果二级主键(个人理解是主键加上二级索引列)能够覆盖查询,则可以避免对主键索引的二次查询。
MySQL查询优化器会在执行查询前判断是否有一个索引能进行覆盖,如果条件为假(false),MySQL 5.5和更早的版本也总是会回表获取数据行。(条件为false的一个典型例子:索引覆盖了WHERE条件中的字段,但不是整个查询涉及的字段)
一个无法覆盖查询的例子:
SELECT * FROM products WHERE actor='SEAN CARREY'
-> AND title like '%APOLLO%'\G
这里索引无法覆盖该查询,有两个原因:
没有任何索引能够覆盖这个查询。因为查询从表中选择了所有的列,而没有任何索引覆盖了所有的列。
MySQL不能在索引中执行LIKE操作。MySQL能在索引中做最左前缀匹配的LIKE比较,,但是如果是通配符开头的LIKE查询,存储引擎就无法做比较匹配。
InnoDB的二级索引的叶子节点都包含了主键的值,这意味着InnoDB的二级索引可以有效地利用这些“额外”的主键列来覆盖查询。
sakila.actor使用InnoDB存储引擎,并在last_name字段有二级索引,虽然该索引的列不包括主键actor_id,但也能够用于对actor_id做覆盖查询:
SELECT actor_id, last_name
-> FROM sakila.actor WHERE last_name = 'HOPPER'\G