创建高性能的索引

Posted by 淦 Blog on July 28, 2025

索引基础

索引的类型

在MySQL中,索引是在存储引擎层而不是服务器层实现的。所以,并没有统一的索引标准:不同存储引擎的索引的工作方式并不一样,也不是所有的存储引擎都支持所有类型的索引。

B-tree索引

  • 按照索引列中的数据大小顺序存储的,很适合按照范围来查询。
  • InnoDB存储引擎有一个被称为自适应哈希索引的特性。当InnoDB发现某些索引值被非常频繁地被访问时,它会在原有的B-tree索引之上,在内存中再构建一个哈希索引。
  • B-tree索引适用于全键值、键值范围或键前缀查找。其中键前缀查找只适用于根据最左前缀的查找。
    1. 全值匹配指的是和索引中的所有列匹配
    2. 匹配最左前缀
    3. 匹配列前缀
    4. 匹配范围值
    5. 精确匹配某一列而范围匹配另外一列
    6. 只访问索引的查询

限制

  • 如果不是按照索引的最左列开始查找,则无法使用索引。
  • 不能跳过索引中的列。
  • 如果查询中有某列的范围查询,则其右边所有列都无法使用索引优化查找。

全文索引

FULLTEXT是一种特殊类型的索引,它查找的是文本中的关键词,而不是直接比较索引中的值。

使用索引的优点

  • 大大减少了服务器需要扫描的数据量
  • 帮助服务器避免排序和临时表
  • 将随机I/O变为顺序I/O

高性能的索引策略

前缀索引和索引的选择性

1
ALTER TABLE sakila.city_demo ADD KEY (city(7));

索引的选择性是指,不重复的索引值(也称为基数,cardinality)和数据表的记录总数(#T)的比值,范围从1/#T到1之间。索引的选择性越高则查询效率越高,因为选择性高的索引可以让MySQL在查找时过滤掉更多的行。唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。

对于BLOB、TEXT或者很长的VARCHAR类型的列,必须使用前缀索引,因为MySQL并不支持对这些列的完整内容进行索引。

既要选择足够长的前缀以保证较高的选择性,同时又不能太长(以便节约空间)。前缀应该足够长,以使得前缀索引的选择性接近于索引整列。即前缀的“基数”应该接近于完整列的“基数”。

前缀索引是一种能使索引更小、更快的有效办法,但它也有缺点:MySQL无法使用前缀索引做ORDER BY和GROUP BY操作,也无法使用前缀索引做覆盖扫描。

多列索引

为每列创建独立的索引。在最好的情况下也只能是“一星”索引,其性能比起真正最优的索引可能差几个数量级。

有时如果无法设计一个“三星”索引,那么不如忽略掉WHERE子句,集中精力优化索引列的顺序,或者创建一个全覆盖索引。

选择合适的索引列顺序

将选择性最高的列放到索引最前列。在很多场景中可能有帮助,但是要全面地考虑各种场景的话,考虑如何避免大量随机I/O和排序可能更重要。(场景不同则选择不同,没有一个放之四海皆准的法则。这里只是说明,这个经验法则可能没有你想象中那么重要。)

看看各个WHERE条件的分支对应的数据基数有多大

关于选择性和基数的经验法则值得去研究和分析,但一定别忘了查询子句中的排序、分组和范围条件等其他因素,这些因素可能会对查询的性能造成非常大的影响。

聚簇索引

聚簇索引并不是一种单独的索引类型,而是一种数据存储方式。具体的细节依赖于其实现方式,但InnoDB的聚簇索引实际上在同一个结构中保存了B-tree索引和数据行。当表有聚簇索引时,它的数据行实际上存放在索引的叶子页(leaf page)中。术语“聚簇”表示数据行和相邻的键值紧凑地存储在一起。

覆盖索引

如果一个索引包含(或者说覆盖)所有需要查询的字段的值,就称之为覆盖索引。需要注意的是,只有B-tree索引可以用于覆盖索引。

使用索引扫描来做排序

MySQL有两种方式可以生成有序的结果:通过排序操作,或者按索引顺序扫描。如果在EXPLAIN的输出结果中,type列的值为“index”,则说明MySQL使用了索引扫描来做排序(注意,不要和Extra列的“Using index”搞混)。

冗余和重复索引

重复索引是指在相同的列上按照相同顺序创建的相同类型的索引。应该避免创建这样的重复索引,发现以后应该立即移除。

未使用的索引

除了冗余索引和重复索引,可能还会有一些服务器永远不用的索引。这样的索引完全是累赘,建议删除。

维护索引和表

找到并修复损坏的表

损坏的索引会导致查询返回错误的结果或者出现莫须有的主键冲突等问题,严重时甚至还会导致数据库的崩溃。如果遇到了古怪的问题——例如一些不应该发生的错误——可以尝试运行CHECK TABLE来检查是否发生了表损坏(注意,有些存储引擎不支持该命令;而有些存储引擎则支持以不同的选项来控制检查表的强度)。CHECK TABLE通常能够找出大多数的表和索引的错误。

使用REPAIR TABLE命令来修复损坏的表,但同样不是所有的存储引擎都支持该命令。

如果遇到数据损坏,最重要的是找出是什么导致了损坏,而不只是简单地修复,否则很有可能还会不断地出现数据损坏的情况。可以通过设置innodb_force_recovery参数进入InnoDB的强制恢复模式来修复数据。

更新索引统计信息

如果存储引擎向优化器提供的扫描行数信息不准确,或者执行计划本身太复杂以致无法准确地获取各个阶段匹配的行数,那么优化器会使用索引统计信息来估算扫描行数。

MySQL的优化器使用的是基于成本的模型,而衡量成本的主要指标就是一个查询需要扫描多少行。如果表没有统计信息,或者统计信息不准确,优化器就很有可能做出错误的决定。可以通过运行ANALYZE TABLE来重新生成统计信息,以解决这个问题。

减少索引和数据的碎片

通过执行OPTIMIZE TABLE或者导出再导入的方式来重新整理数据。这对多数存储引擎都是有效的。