联合索引是一棵B+树而非多个索引,按定义列顺序排序,必须遵循最左前缀原则才能有效使用;范围查询列应置于联合索引末尾,且需通过EXPLAIN验证key和key_len字段确认索引实际使用情况。

联合索引不是多个索引,而是一棵B+树
很多人误以为 CREATE INDEX idx_a_b_c ON t(a,b,c) 会生成 (a)、(a,b)、(a,b,c) 三棵树——其实不会。MySQL只建一棵B+树,排序规则是:先按 a 升序,a 相同时再按 b 升序,a 和 b 都相同时再按 c 升序。这就决定了查询必须“从左开始连续匹配”,否则无法定位到有序区间。
- 能用索引:
WHERE a = 1、WHERE a = 1 AND b = 2、WHERE a = 1 AND b = 2 AND c > 10 - 不能用索引:
WHERE b = 2(跳过a)、WHERE a = 1 AND c = 3(b中断,c无法继续走索引) - 注意:
WHERE b = 2 AND a = 1看似顺序反了,但MySQL查询优化器会自动重排为a = 1 AND b = 2,仍可命中索引
范围查询(>、
这是最常踩的坑。一旦联合索引中某列用了范围条件,它右边所有列就**彻底失去索引加速能力**,哪怕你写了等值条件也不行。
SELECT * FROM orders WHERE user_id = 1001 AND status = 1 AND create_time > '2024-01-01';
这句只能用上 (user_id, status),create_time 的范围判断之后,索引就“断”了——即使你后面还加了 AND pay_amount = 99.9,这个 pay_amount 也查不到索引。
- 正确做法:把范围列尽量放在联合索引末尾,如
(user_id, status, create_time) - 错误设计:若常用
WHERE status = 1 AND create_time BETWEEN ... AND ...,却把status放在第二位,那这个查询根本用不上索引 -
LIKE 'abc%'是范围行为,LIKE '%abc'则完全不走索引(连最左都不满足)
explain 是唯一能验证你是否真用上索引的工具
别靠“我写了 where a=1 and b=2”就以为索引生效了。必须看 EXPLAIN 的 key 和 key_len 字段:
-
key显示实际使用的索引名 → 确认没走全表扫描 -
key_len显示用了索引多少字节 → 比如idx_user_status_time中user_id是INT(4字节),status是TINYINT(1字节),那么key_len = 5表示只用到了前两列;若key_len = 4,说明只用到了user_id,status没参与索引查找 - 执行
EXPLAIN FORMAT=TRADITIONAL SELECT ...,重点关注type(最好为ref或range),避免出现ALL
索引字段顺序不是“哪个查得多就放前面”,而是按查询模式定
有人觉得“status 取值少、区分度低,应该放后面”,但现实是:如果你的高频查询是 WHERE status = 'paid' AND user_id IN (1,2,3),那这个索引 (status, user_id) 就比 (user_id, status) 更合适——因为前者能直接定位到所有 status='paid' 的数据块,再在其中快速筛选 user_id;后者却要先遍历每个 user_id 对应的索引项,再回表判断 status,效率反而更低。
- 等值查询多、范围查询少 → 把高选择性(区分度高)字段放前面
- 固定前缀 + 范围查询 → 把等值列全放左,范围列放右,如
(shop_id, category_id, created_at) - 存在多个高频查询组合?优先覆盖最耗性能的那条,或拆成两个索引(空间换时间)
真正容易被忽略的是:最左前缀原则不是“语法限制”,而是B+树物理结构决定的刚性约束。写SQL时字段顺序无关紧要,但索引定义的列序一旦定下,就锁死了所有可能的查询路径。改索引成本远高于改SQL,所以建之前就得想清楚主查询模式。










