联合索引的最左前缀与覆盖索引:一次讲清楚

米阳 2022-12-10 4 12/10

联合索引的规则说起来就一句“最左前缀”,但为什么是这样、什么情况下会失效、覆盖索引又怎么省掉回表,值得用具体的例子走一遍。

B+ 树里到底按什么排序

CREATE INDEX idx_abc ON t (a, b, c);

这棵索引树按 a 排序;a 相同的时候按 b 排序;a、b 都相同才按 c 排序。就像电话簿按“姓、名”排序——你可以快速找到“张”姓的所有人,也可以找“张伟”,但没法直接找“名叫伟”的所有人。

所以:

WHERE a=1 AND b=2 AND c=3   -- 全用上
WHERE a=1 AND c=3           -- 只用上 a
WHERE b=2 AND c=3           -- 完全用不上
WHERE a=1 AND b>2 AND c=3   -- 只用上 a、b(c 失去有序性)

范围查询后面的列为什么用不上

b > 2 命中多行,这些行里 c 的排列并不全局有序(只在每个 b 值内部有序),MySQL 不能再用 c 做定位,只能在过滤出的行里逐条比较。这就是“范围之后失效”。

如果业务确实要 a=1 AND b>2 AND c=3,可以把索引改成 (a, c, b),让等值条件 c 排在范围条件 b 前面。

覆盖索引:不回表

普通二级索引的叶子节点存的是主键值。查到索引项之后还要拿主键回主键索引取整行,这叫回表。如果查询需要的列全都在索引里,就不用回表:

-- 只查 a、b,索引里都有
SELECT a, b FROM t WHERE a = 1;
-- Extra: Using index

Using index 就是覆盖索引生效的标志。列表页这种“只取少量字段、数据量大”的场景,覆盖索引收益非常明显——可能从几千次随机 IO 变成一次顺序扫描。

代价是索引变宽。把 title 这种长字符串塞进索引,索引文件会膨胀好几倍,写入也变慢。需要权衡。

索引列顺序怎么定

三条实用原则:

  1. 区分度高的列放前面(status 只有三种值,放前面过滤效果差)
  2. 等值条件放前面,范围条件放后面
  3. 把 ORDER BY 的列放在最后,可以顺带消掉 filesort

一个真实例子

文章列表查询:WHERE status='publish' AND lang='zh' ORDER BY created_at DESC LIMIT 20。

索引建成 (status, lang, created_at):两个等值条件定位,created_at 有序,取前 20 行就停。如果顺序建成 (created_at, status, lang),就只能沿时间倒序扫,逐行判断状态,扫几千行才凑够 20 条。

同一个查询,索引列顺序不同,性能差了两个数量级。这就是为什么建索引不能只看“哪些列出现在 WHERE 里”,要看完整的查询形态。

- THE END -

米阳

12月10日10:35

最后修改:2022年12月10日
0

非特殊说明,本博所有文章均为博主原创。

共有 0 条评论