本文旨在阐述 MySQL 的索引使用时,需要注意的点。

概述

InnoDB 存储数据的原理

MySQL 的 InnoDB 引擎,为了减少磁盘的IO次数,将数据被分成若干页,以页为单位保存在磁盘中。InnoDB 的页大小,一般是 16KB。

聚簇索引和二级索引

InnoDB 使用 B+ 树,既可以保存实际数据,也可以加速数据搜索,这就是聚簇索引由于数据在物理上只会保存一份,所以包含实际数据的聚簇索引只能有一个,即 B+ 树的叶子节点

聚簇索引就是主键,而为了实现非主键字段的快速搜索,就引出了二级索引,也叫作非聚簇索引、辅助索引。二级索引的叶子节点中保存的不是实际数据,而是主键,获得主键值后去聚簇索引中获得数据行。这个过程就叫作回表

覆盖索引

联合索引中保存了多个索引列的值。如果查询的是索引列索引或联合索引能覆盖的数据,那么查询索引本身已经“覆盖”了需要的数据,不再需要回表查询。这种情况也叫作索引覆盖

如建立了联合索引(name,score)之后,执行查询:

1
select name,score from person where name = 'name1';

注意点一:不能认为索引越多越好

额外创建二级索引的代价

维护代价

创建多个二级索引,在新增数据时,除了要维护聚簇索引,还有维护多个二级索引。

页中的记录都是按照索引值从小到大的顺序存放的,新增记录就需要往页中插入数据,现有的页满了就需要新创建一个页,把现有页的部分数据移过去,这就是页分裂;如果删除了许多数据使得页比较空闲,还需要进行页合并。页分裂和合并,都会有 IO 代价,并且可能在操作过程中产生死锁

结论:应该设置合理的合并阈值,来平衡页的空闲率和因为再次页分裂产生的代价,参考文档

空间代价

虽然二级索引不保存原始数据,但要保存索引列的数据,所以会占用更多的空间。

总结

  1. 无需一开始就建立索引,可以等到业务场景明确后,或者是数据量超过 1 万、查询变慢后,再针对需要查询、排序或分组的字段创建索引。创建索引后可以使用 EXPLAIN 命令,确认查询是否可以使用索引。
  2. 尽量索引轻量级的字段,比如能索引 int 字段就不要索引 varchar 字段。索引字段也可以是部分前缀,在创建的时候指定字段索引长度。针对长文本的搜索,可以考虑使用 Elasticsearch 等专门用于文本搜索的索引数据库。
  3. 尽量不要在 SQL 语句中 SELECT *,而是 SELECT 必要的字段,甚至可以考虑使用联合索引来包含我们要搜索的字段,既能实现索引加速,又可以避免回表的开销。

注意点二:不能认为建了索引就一定有效

索引失效的情况

索引只能匹配列前缀

索引 B+ 树中行数据按照索引值排序,只能根据前缀进行比较。如果要按照后缀搜索也希望走索引的话,并且永远只是按照后缀搜索的话,可以把数据反过来存,用的时候再倒过来。

如,使用name like '%name1'时索引失效。

条件涉及函数操作无法走索引

索引保存的是索引列的原始值,而不是经过函数计算后的值。如果需要针对函数调用走数据库索引的话,只能保存一份函数变换后的值,然后重新针对这个计算列做索引。

如,使用LENGTH(name) = 7时索引失效。

联合索引只能匹配左边的列

在联合索引的情况下,数据是按照索引第一列排序,第一列数据相同时才会按照第二列排序。

基于成本选择索引

成本估算

MySQL 在查询数据之前,会先对可能的方案做执行计划,然后依据成本决定走哪个执行计划。

这里的成本,包括 IO 成本和 CPU 成本

  • IO 成本,是从磁盘把数据加载到内存的成本。默认情况下,读取数据页的 IO 成本常数是 1(也就是读取 1 个页成本是 1)。
  • CPU 成本,是检测数据是否满足条件和排序等 CPU 操作的成本。默认情况下,检测记录的成本是 0.2。

注意

  • MySQL 选择索引,并不是按照 WHERE 条件中列的顺序进行的;
  • 即便列有索引,甚至有多个可能的索引方案,MySQL 也可能不走索引。

分析索引选择(重要)

在 MySQL 5.6 及之后的版本中,我们可以使用 optimizer trace 功能查看优化器生成执行计划的整个过程。有了这个功能,我们不仅可以了解优化器的选择过程,更可以了解每一个执行环节的成本,然后依靠这些信息进一步优化查询。可以查看文档