数据库索引设计:覆盖索引与索引合并
「慢 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 的工程化定位
- 开启
EXPLAIN ANALYZE,看实际行数与预估。 - 在慢日志里抓 95 分位以上的 SQL。
- 逐条执行
EXPLAIN看是否用上索引、是否有回表。 - 必要时用 SQL 改写、组合索引、覆盖索引三种方式打补丁。
SQL 改写的几种妙招
- 把
OR改UNION。 - 把
SELECT *改具体列。 - 把函数包裹列(如
DATE(col))去掉。 - 把
!=改成IN。 - 把
LIKE '%xxx%'改成全文或反查。
每一招都来自线上实战。
把索引变成「工程纪律」
索引的设计应当变成团队的工程纪律:
- 每个新表上线要带索引设计文档。
- 大表的索引变更走 DBA review。
- 慢 SQL 审核纳入 SRE 周会。
- 每季度审视一次无用索引。
给团队的简短总结
- 能加组合索引就别加单列索引。
- 能覆盖就不回表。
- 能用最左就别跨列。
- 能 ICP 就不 server filter。
- 能改写就别建新索引。
索引设计的功夫是慢功夫。但凡把上面几条在团队里固化下来,半年内就能看到整体 P99 下降 30%。这是非常划算的回报。