人人都会AI编程

12.4 索引优化技巧

更新时间:2026-07-10

索引并非建得越多越好,关键在于让每一次查询都尽可能少地与磁盘交互。下面三个优化技巧,都是从减少回表次数或缩小索引体积的角度出发,在实际开发中性价比极高。

12.4.1 覆盖索引:让查询在索引树上完成,避免回表

回表是指通过二级索引查到主键后,还要再到聚簇索引中读取完整行数据。如果查询所需的所有列都包含在索引中,那么数据库直接在索引树上就能返回结果,不需要回表,这就是覆盖索引

覆盖索引的核心价值在于:将“索引扫描+回表”两次操作压缩为“索引扫描”一次操作。尤其在数据量大、回表次数多时,性能差距可达数倍甚至数十倍。

真实场景:
一个用户表 users(id, name, age, mobile, email),对 age 建了普通索引。若执行:

SELECT id, age FROM users WHERE age > 25;

age 索引的叶子节点存储的是 (age, id),查询需要的 idage 都在索引里,可以直接返回,这就是覆盖索引。反之,若需要 mobile 字段,索引里没有,就必须拿着 id 回表。

如何设计覆盖索引:

  • 不要只为 WHERE 条件建索引,还要考虑 SELECT 中的列,让它们也进入索引树。
  • 常用技巧是建联合索引,例如对 (age, mobile) 建索引,查询 SELECT id, age, mobile FROM users WHERE age > 25 就能完全覆盖。
  • 注意:SELECT * 几乎不可能利用覆盖索引,建议只查真正需要的字段。

几个值得注意的点:

  • 覆盖索引只对当前索引能覆盖的列有效,多余字段仍需回表。
  • 太多的列放在索引里会增大索引体积,影响写入性能,平衡点在于:覆盖索引通常用于高频关键查询。
  • EXPLAINExtra 列出现 Using index(而非 Using index condition),代表完全覆盖且不需要回表。

12.4.2 索引下推:让引擎层提前过滤,减少回表

在某些查询中,尽管索引不能完全覆盖 SELECT 列,但可以在索引扫描过程中提前过滤掉不满足条件的记录,从而减少后续的回表次数。这个能力在 MySQL 5.6 中引入,称为索引下推(Index Condition Pushdown, ICP)

原理对比:
假设有联合索引 idx_name_age (name, age),执行:

SELECT * FROM users WHERE name LIKE '张%' AND age = 20;
  • 无 ICP 时:存储引擎通过索引找到所有 name LIKE '张%' 的记录,每一条都回表取完整行,再交给 Server 层判断 age = 20。即使很多记录的 age 不满足,也白白回表了。
  • 有 ICP 时:存储引擎在遍历索引时,直接检查索引中的 age 字段是否等于 20,只将满足条件的记录回表。因为 age 就在联合索引里,不需要读完整行就能判断。这就大幅减少了不必要的回表。

实际效果:name 过滤后仍然有大量不符合 age 条件的场景,ICP 带来的性能提升非常明显。如果索引中已包含过滤字段,ICP 可以减少大量随机 I/O。

查看方式: EXPLAINExtra 显示 Using index condition 即表示使用了索引下推。

注意: 索引下推不仅适用于范围查询,很多情况下优化器会自动选择是否启用,开发者只需理解其原理,并在设计索引时将经常联合过滤的字段放在合适的索引列中,就能自然享受到该优化。

12.4.3 前缀索引:用更小的索引体积优化长字符串

当需要对很长的字符串列(如 VARCHAR(255) 的邮箱、地址、长文本摘要)建索引时,完整索引体积会非常大,拖慢写入并占用大量内存。前缀索引只对字符串的前几个字符建立索引,大幅节省空间。

语法:

CREATE INDEX idx_email ON users(email(15));  -- 对email的前15个字符建索引

适用场景:

  • 字符串较长但前几个字符区分度已经足够高。比如 email 字段,前 15 个字符大概率能区分绝大多数用户。
  • URL、长文本开头等。

前缀长度的选择非常关键: 太短则区分度差,索引过滤出的数据仍然很多;太长则节约效果不明显。核心指标是前缀的选择性(索引中不重复的索引值数与数据表总记录数的比率)。通常要使区分度接近完整列的区分度。

计算方法:

-- 先计算完整列的区分度
SELECT COUNT(DISTINCT email) / COUNT(*) FROM users;
-- 再计算不同前缀长度的区分度
SELECT COUNT(DISTINCT LEFT(email, 10)) / COUNT(*) FROM users;
SELECT COUNT(DISTINCT LEFT(email, 15)) / COUNT(*) FROM users;
-- 选择接近完整区分度且长度较短的方案

前缀索引的代价和限制:

  • 无法使用前缀索引做 ORDER BY 和覆盖索引,因为索引中只存了前缀,无法精确排序,也无法回表后补充剩余字符。
  • 对隐式排序的 DISTINCTGROUP BY 也有影响。
  • WHERE 条件中使用前缀索引时,优化器会通过前缀定位到记录,但仍需要回表读取完整值并比对(即二次判断),因为前缀相同不代表整列相同。

因此,前缀索引是一种空间换时间失败后反向操作的技巧:当整个字段无法全量索引或索引体积成为瓶颈时,它是最优解。对于区分度高、长度适中的列,尽量不要前缀,直接用完整索引;对于超长且前缀区分度够的场景,前缀索引是务实的方案。


这三个技巧都指向同一个目标:让索引在更少的磁盘 I/O 下返回正确的结果。覆盖索引消灭回表,索引下推减少回表次数,前缀索引降低索引体积让索引更高效。日常 SQL 优化中,观察 Extra 列、分析是否回表,是突破性能瓶颈的快捷路径。