java面试必问16:最左前缀原则:复合索引的灵魂,一点就懂
最左前缀原则:复合索引的灵魂,一篇讲透
面试官:“复合索引 (a, b, c),查询条件
WHERE b = 1 AND c = 2能用索引吗?”
你:“不能,因为跳过了最左列 a,索引失效。”
面试官:“那WHERE a = 1 AND c = 2呢?”
你:“只能用到 a,c 无法使用索引,因为中间跳过了 b。”
面试官:“为什么会有这个原则?”
你:“……”
很多人能背出“最左前缀”,但一追问“为什么”“如何优化”就含糊了。本文从 B+Tree 结构出发,讲透复合索引的匹配规则和优化技巧。
一、什么是最左前缀原则?
最左前缀原则:复合索引(联合索引)使用时,必须从索引的最左列开始匹配,不能跳过中间的列。只有遵守这个顺序,索引才能充分发挥作用。
以复合索引(a, b, c)为例,它能有效支持的查询条件模式有:
a = ?a = ? AND b = ?a = ? AND b = ? AND c = ?
不能有效使用索引的情况:
b = ?(缺少最左列 a)c = ?(缺少 a、b)a = ? AND c = ?(跳过 b,只能用 a,c 无法走索引)
注意:WHERE a = ? AND c = ?并不是完全不能用索引,而是只能用(a)部分过滤,c的条件无法在索引中直接定位,需要回表后再过滤。
二、为什么会有最左前缀原则?—— B+Tree 的结构决定
复合索引在 B+Tree 中的排序规则是:先按第一列排序,第一列相同再按第二列排序,以此类推。
例如索引(name, age)存储的数据逻辑顺序:
| name | age |
|---|---|
| 张三 | 20 |
| 张三 | 25 |
| 李四 | 18 |
| 李四 | 22 |
| 王五 | 30 |
可以看到:
- 所有
name相同的记录是连续存放的。 - 在
name相同的前提下,age是有序的。 - 但如果只按
age搜索(比如age = 20),age在整个索引中并不是全局有序的(20 出现在不同 name 段中),因此无法使用索引快速定位。
结论:索引的有序性是基于最前列建立的,跳过了最前列,后续列的有序性就失去了意义。
三、哪些情况能用到索引?
假设索引(a, b, c),以下查询都能有效利用索引:
| 查询条件 | 索引使用情况 |
|---|---|
a = 1 | ✅ 完全使用索引 |
a = 1 AND b = 2 | ✅ 使用 a、b |
a = 1 AND b = 2 AND c = 3 | ✅ 使用 a、b、c |
a = 1 AND c = 3 | ⚠️ 只用到 a(c 无法使用,但 a 可过滤) |
a = 1 AND b > 2 AND c = 3 | ⚠️ 用到 a 和 b(范围查询后 c 无法使用) |
a IN (1,2) AND b = 2 | ✅ 用到 a 和 b(IN 相当于多个等值) |
a = 1 ORDER BY b | ✅ 索引已排序,避免 filesort |
a = 1 AND b = 2 ORDER BY c | ✅ 索引有序,无需额外排序 |
四、哪些情况索引失效或效率下降?
| 查询条件 | 原因 |
|---|---|
b = 2 | 缺少最左列 a |
c = 3 | 缺少 a、b |
a = 1 AND c = 3 | 跳过 b,c 无法走索引 |
a > 1 AND b = 2 | 范围查询后 b 无法使用(a 后的列全部失效) |
a = 1 OR b = 2 | OR 前后未全索引(除非 a 和 b 分别有索引) |
特别说明:a > 1 AND b = 2中,a 是范围查询,b 的条件无法在索引中直接定位,因为索引在 a 范围内,b 不是有序的(a 不同,b 可能乱序)。优化器只能用到 a 的过滤,b 需要回表后过滤。
五、如何设计高效的复合索引?
1. 区分度高的列放在左边
将筛选能力最强的列放在最左,可以快速缩小数据范围。例如,(status, create_time)比(create_time, status)更好(如果 status 只有几个值,则 create_time 更合适)。
2. 等值查询的列放在左边,范围查询的列放在右边
WHERE a = 1 AND b > 2 AND c = 3:索引(a, b, c)中,b 是范围查询,导致 c 无法使用。可以调整索引为(a, c, b),如果 c 是等值查询,可以先过滤。
3. 将经常排序的列加入索引
如果经常ORDER BY b,且WHERE a = 1,索引(a, b)可以避免 filesort。
4. 覆盖索引优先
尽量让索引包含查询所需的所有列,避免回表。例如SELECT a, b FROM t WHERE a = 1,索引(a, b)就是覆盖索引。
六、常见面试追问
Q1:WHERE a = 1 AND c = 2完全不能用索引吗?
不是完全不能用。MySQL 可以使用索引的(a)部分过滤,但c无法在索引中定位。优化器可能选择使用索引(如果 a 的区分度高),然后回表再过滤 c;也可能直接全表扫描(如果 a 的区分度低)。
Q2:WHERE a IN (1,2) AND b = 3能用索引吗?
能。IN相当于多个等值,MySQL 会分别对每个值使用索引。执行计划中type通常为range。
Q3:WHERE a = 1 AND b = 2 ORDER BY c能避免排序吗?
可以。因为索引(a, b, c)已经按 a、b、c 排序,ORDER BY c是在 a=1 且 b=2 的范围内,c 自然有序,无需 filesort。
Q4:如果查询条件是WHERE a > 1 AND a < 10 AND b = 2,如何优化?
范围查询a会导致 b 无法使用。可以考虑将 b 放在前面:索引(b, a),先等值 b,再范围 a。或者使用IN改写(如果 a 的值可枚举)。
Q5:复合索引最多可以包含多少列?
MySQL 限制最多 16 列,但实际建议不超过 5 列,否则维护成本高,且区分度可能下降。
七、总结
索引(a,b,c)查询条件 | 索引使用情况 |
|---|---|
a=1 | ✅ 全用 |
a=1 AND b=2 | ✅ 全用 |
a=1 AND b=2 AND c=3 | ✅ 全用 |
a=1 AND c=2 | ⚠️ 只用 a |
b=2 | ❌ 失效 |
a>1 AND b=2 | ⚠️ 只用 a(范围后失效) |
a=1 AND b>2 AND c=3 | ⚠️ 用 a、b(c 失效) |
一句话记住最左前缀:最左先,等值前,范围后,全失效。
理解最左前缀原则,是设计高效复合索引的基础。在日常开发中,分析查询模式,将最常用的等值条件放在索引左侧,范围条件放在右侧,能显著提升查询性能。
希望这篇文章能帮你彻底掌握这个高频考点,欢迎继续讨论。
