什么是联合索引的最左前缀原则?
简化版
最左前缀原则是指联合索引按从左到右的列顺序建立有序结构,查询条件必须从最左列开始连续使用,才能充分利用索引。比如索引 (a, b, c) 可以支持 a、a,b、a,b,c,但通常不能直接跳过 a 只用 b,c。
详细版
联合索引 (a, b, c) 的排序方式可以理解为:
- 先按
a排序; a相同再按b排序;a、b都相同再按c排序。
因此它能高效支持这些条件:
where a = ?
where a = ? and b = ?
where a = ? and b = ? and c = ?
where a = ? and b between ? and ?
但这些写法通常不能完整使用索引:
where b = ?
where b = ? and c = ?
where a > ? and b = ?
最后一个例子里,a 用了范围条件后,b 可能还能在某些版本和场景下做索引条件下推或过滤,但不能继续像等值列那样用于精准缩小 B+ 树搜索范围。
完整版教学
一、联合索引不是多个单列索引的简单相加
(a, b, c) 是一棵按多个字段拼起来排序的 B+ 树,不是三棵索引。它的 key 形态类似:
(a1, b1, c1)
(a1, b1, c2)
(a1, b2, c1)
(a2, b1, c1)
如果没有 a 的条件,单独找 b = 10,在整棵树上看,b 并不是全局有序的。它只在 a 相同的小范围内有序。所以跳过最左列,优化器通常无法用这棵树快速定位。
可以把联合索引的排序想成字典排序:先比较第 1 个字母,第 1 个相同再比较第 2 个,第 2 个相同再比较第 3 个。(province, city, user_id) 也是一样,先按省份聚在一起,再在同一个省份内按城市排序,最后才在同一个城市内按用户排序。如果查询没有省份,只给城市,索引上所有省份下面都可能出现这个城市,定位就变成了分散扫描。
联合索引 (a,b,c) 的局部有序性:
a=1: (1,1,1) (1,1,2) (1,2,1) (1,3,1)
a=2: (2,1,1) (2,2,1) (2,2,2) (2,3,1)
a=3: (3,1,1) (3,1,2) (3,2,1) (3,3,1)
b 在每个 a 分组内有序,但脱离 a 后不是全局连续区间。
二、等值列、范围列、排序列怎么安排
设计联合索引时,可以按这个思路:
- 高频过滤字段放前面;
- 等值条件列优先放前面;
- 范围条件列放在等值列后面;
- 需要排序的列尽量接在过滤列后面。
例如常见查询:
select *
from orders
where user_id = ?
and status = ?
and create_time between ? and ?
order by create_time desc;
可以考虑索引 (user_id, status, create_time)。前两列等值过滤,第三列范围查询和排序都能受益。
面试里不要只背“区分度高的放前面”,因为真实索引服务的是访问路径。假设订单表有 1000 万行,user_id=1001 能筛到 500 行,status='PAID' 能筛到 400 万行,显然先用 user_id 更划算;但如果某个报表每天固定查 status='PAID' and create_time between 今天 and 明天,并且再按时间分页,(status, create_time) 可能比把 user_id 放进去更贴合查询。
| 查询特征 | 联合索引设计关注点 | 原因 |
|---|---|---|
| 多个等值条件 | 高频、选择性、排序需求一起看 | 等值条件之间优化器可重排,但索引定义顺序决定物理有序性 |
| 等值 + 范围 | 等值列在前,范围列靠后 | 等值先缩小分组,范围再扫描连续区间 |
| 过滤 + 排序 | 排序列接在等值过滤列后 | 同一等值分组内才能直接利用后续列顺序 |
| 只覆盖返回列 | 适当把小字段放进索引 | 可以减少回表,但索引会变宽 |
记忆钩子:联合索引设计不是“谁区分度最高谁永远第一”,而是让 MySQL 能沿着 B+ 树从左到右走出一条连续、便宜的访问路径。
三、范围条件为什么容易“截断”
联合索引的有序性是逐列建立的。当前面的列是等值时,后面的列还能继续用于定位;当前面的列变成范围时,范围内会包含多个不同值,后续列的全局顺序就不再稳定。
以 (a, b) 为例:
where a > 10 and b = 5
a > 10 会扫描一段范围,这个范围里有很多不同的 a,每个 a 内部的 b 是有序的,但整个扫描范围内的 b 不是一个可以直接跳到 b=5 的连续区间。因此后续列的使用能力会明显下降。
更具体地看,索引 (a,b) 中如果 a in (11,12,13),满足 b=5 的记录会分布在三个不同的 a 分组里:
a=11: b=1 b=5 b=9
a=12: b=2 b=5 b=8
a=13: b=1 b=5 b=7
a > 10 让扫描范围跨过多个 a 分组,b=5 不再是一段全局连续的位置。MySQL 可能仍能利用 b 做索引条件下推,在扫描索引记录时先过滤掉 b != 5 的记录,减少回表;但这和“直接用 b 继续定位”不是一回事。回答时把“定位能力”和“过滤能力”分开,面试官会觉得你真的理解执行过程。
四、最左前缀也适用于 ORDER BY
联合索引不仅能过滤,也能帮助排序。比如索引 (user_id, create_time):
where user_id = ?
order by create_time desc
当 user_id 是等值条件时,索引里同一个用户的数据按 create_time 有序,MySQL 可以少做额外排序。但如果没有 user_id 条件,直接 order by create_time,这个索引通常不合适,因为 create_time 不是全局第一排序键。
排序能不能利用联合索引,也要看是否保持了索引顺序。比如 (a,b,c) 下,where a = 1 order by b, c 通常比较友好,因为在 a=1 这个连续范围内,数据天然按 b,c 排好;但 where a = 1 order by c 就跳过了 b,无法直接得到全局按 c 的顺序。
-- 更容易利用 (a,b,c) 的排序
where a = 1
order by b, c
-- 跳过 b,通常不能直接利用完整排序
where a = 1
order by c
如果排序方向混用,也要结合 MySQL 版本和索引定义判断。MySQL 8.0 支持降序索引,但面试回答通常先讲原则:过滤列先固定住左侧前缀,排序列才能沿着后续有序性发挥作用。
五、用几个 SQL 判断索引用到哪里
假设有索引 idx_abc(a,b,c),可以用下面这组例子训练判断能力:
| SQL 条件 | 能否高效利用左前缀 | 说明 |
|---|---|---|
where a = 1 and b = 2 | 可以用到 a,b | 从左连续等值 |
where b = 2 and a = 1 | 可以用到 a,b | SQL 书写顺序不重要,优化器可调整等值条件 |
where a = 1 and c = 3 | 主要用 a | 跳过 b,c 多数只能过滤 |
where a = 1 and b > 2 and c = 3 | 定位到 a,b 范围 | c 不能像等值列那样继续缩小定位 |
where a = 1 order by b,c | 排序友好 | a 固定后,b,c 仍有序 |
这里的“用到”要说清层次:可能用于快速定位、可能用于索引过滤、也可能只在 Server 层过滤。实际生产环境要用 EXPLAIN 看 key、key_len、Extra,但面试题考的是你能否从 B+ 树排序规则推导出大方向。
六、联合索引设计的成本边界
联合索引能提升查询,也会增加写入成本和存储成本。每次插入、更新索引列、删除记录时,InnoDB 都要维护对应 B+ 树;索引越多、越宽,写入越慢,占用页越多,Buffer Pool 能缓存的数据也会被稀释。
例如一张订单表每天新增 200 万行,如果为了 3 个低频后台查询各建一个 5 列联合索引,写入链路可能持续为低频查询付出维护成本。更稳妥的做法是围绕高频查询、慢查询和核心分页接口设计少量高价值索引,并定期清理被左前缀覆盖的冗余索引。
七、常见误区与追问
- 误区:建了
(a,b)后还保留(a)一定更快。 很多场景下(a,b)已经能按左前缀支持where a = ?,单独的(a)可能是冗余索引;是否保留要看索引宽度、覆盖需求和优化器选择。 - 误区:SQL 条件书写顺序必须和索引顺序一致。
where b = ? and a = ?通常仍可被优化器整理为使用(a,b),真正关键的是索引的物理定义顺序和条件类型。 - 误区:范围条件后面的列完全没用。 后续列通常不能继续做精准定位,但在某些场景能用于索引条件下推或索引过滤,作用从“定位”下降为“减少回表或减少传给 Server 的记录”。
- 误区:区分度最高的列永远放联合索引最左。 区分度很重要,但还要看等值、范围、排序、分组、分页和高频 SQL;脱离查询模式谈索引顺序容易设计错。
- 追问:
where a = ? and c = ?能用(a,b,c)吗? 通常可以用a定位左前缀,c可能作为过滤条件,但因为跳过b,不能完整利用到a,b,c的连续有序性。 - 追问:最左前缀原则只影响
where吗? 不是,它也影响order by、group by和覆盖索引判断;只要依赖联合索引的有序性,就要考虑左前缀是否连续。
八、加强记忆
联合索引 (a,b,c) 的本质是多列字典序:先按 a 排,a 相同再按 b,最后才看 c。查询能否用好它,就看能不能从最左列开始连续利用这份有序性;等值列负责缩小分组,范围列负责扫描区间,排序列要接在已固定的前缀后面。回答时把“索引用于定位”和“索引用于过滤”区分开,再结合真实高频 SQL 设计索引,而不是机械背口诀。