一粟 · yisu.app
· 12 min read
索引

数据库索引设计:覆盖索引与索引合并

「慢 SQL」80% 都是索引问题,而其中一半是「索引建错」。本文以 InnoDB 与 LSM-Tree 为样本,剖析聚簇索引、回表、覆盖索引、索引下推、Index Merge 等概念的真实作用。

索引的三个真相

工程师对索引常有三个误解:

  • 「加索引就快了」——盲加索引反而拖慢写入。
  • 「查询快就行」——慢不仅指耗时还包括 P99 抖动。
  • 「主键索引就够了」——大多数查询是范围检索,需要二级索引配合。

好的索引设计是对「查询形态」的预判。接下来的内容会让你看到:

聚簇索引:从 InnoDB 的物理存储说起

InnoDB 的表数据按主键组织成 B+Tree。主键叶子节点存储了完整行数据。这就是「聚簇索引」。一个常见的误解:以为 InnoDB 总是「能用上索引」。事实上没有命中聚簇或二级索引时,仍然是全表扫描。

回表:什么是「最被忽视的开销」

二级索引叶子节点只存「主键 + 索引列」。当查询需要更多列,就要回到聚簇索引拿全行,这就是「回表」。回表随机读,很贵。所以最常见的优化是把需要查询的列加进二级索引,称为「覆盖索引」。

-- 假设业务经常需要:WHERE user_id=? AND created_at > ? ORDER BY id LIMIT 20
-- 同时 SELECT 还要 id, amount, status

CREATE INDEX idx_user_created ON orders(user_id, created_at, amount, status);
-- 现在查询不需要回表 → P99 从 90ms 降到 5ms

Index Merge 与 Bitmap

MySQL 8 在某些条件下支持 Index Merge:把多个单列索引的结果 union / intersect 后再读。这是个「避免单独索引」的折中方案,但实际很少用到,因为它的执行计划远不如「组合索引」稳定。

当遇到 Index Merge 时,第一反应应当是「我能不能改写 SQL 或加组合索引」。

最左前缀与索引顺序

组合索引从最左列开始匹配。如果查询跳过了第一列,索引就废了。这是新手最容易踩的坑:

  • (a, b, c) 索引可服务 a=?a=? AND b=?a=? AND b=? AND c=?
  • 不能服务 b=?b=? AND c=?

设计索引的顺序原则:区分度高 + 过滤强在前

索引下推 ICP

MySQL 5.6+ 引入了 ICP(Index Condition Pushdown)。它让 server 层把「索引列的 WHERE 条件下推」给存储引擎,引擎在拿到行前就能过滤,不必先回表。性能提升在「范围列 + 大量回表」的场景下非常明显。

不可见索引:在生产做实验

MySQL 8 提供「不可见索引」:让优化器在执行计划中忽略它。这等于在线做 A/B 测试:

ALTER TABLE orders ALTER INDEX idx_user_created INVISIBLE;
-- 此时查询走其他索引
-- 数天后回滚
ALTER TABLE orders ALTER INDEX idx_user_created VISIBLE;

LSM 树索引:另一片天地

RocksDB / Cassandra / Elasticsearch / HBase 都用 LSM 树。它的索引形态与 B+Tree 不同:先写 memtable,flush 后成 immutable sstable,多个 sstable 用 compaction 合并。读时查询 memtable + 多级 cache。

这意味着 LSM 的写入极快,范围扫描稍慢。索引设计时要注意:

  • 前缀索引利于写入。
  • 删除/更新要等 compaction 才能生效。
  • Bloom filter 是关键,用于减少无效 sstable 查询。

慢 SQL 的工程化定位

  1. 开启 EXPLAIN ANALYZE,看实际行数与预估。
  2. 在慢日志里抓 95 分位以上的 SQL。
  3. 逐条执行 EXPLAIN 看是否用上索引、是否有回表。
  4. 必要时用 SQL 改写、组合索引、覆盖索引三种方式打补丁。

SQL 改写的几种妙招

  • ORUNION
  • SELECT * 改具体列。
  • 把函数包裹列(如 DATE(col))去掉。
  • != 改成 IN
  • LIKE '%xxx%' 改成全文或反查。

每一招都来自线上实战。

把索引变成「工程纪律」

索引的设计应当变成团队的工程纪律:

  1. 每个新表上线要带索引设计文档。
  2. 大表的索引变更走 DBA review。
  3. 慢 SQL 审核纳入 SRE 周会。
  4. 每季度审视一次无用索引。

给团队的简短总结

  • 能加组合索引就别加单列索引。
  • 能覆盖就不回表。
  • 能用最左就别跨列。
  • 能 ICP 就不 server filter。
  • 能改写就别建新索引。

索引设计的功夫是慢功夫。但凡把上面几条在团队里固化下来,半年内就能看到整体 P99 下降 30%。这是非常划算的回报。