复合索引列顺序决定b+树搜索路径:必须从最左列开始等值匹配,范围查询会截断后续列使用,高频过滤字段应前置,隐式转换或函数导致索引失效。

复合索引列顺序直接影响B+树搜索路径
MySQL的复合索引底层是B+树,它按定义顺序逐列构建排序结构:先排第一列,相同值再排第二列,依此类推。查询时优化器只能从最左列开始“走树”,一旦某列没参与等值匹配(= 或 IN),后续列就彻底失效——这不是语法限制,而是物理存储决定的必然行为。
-
INDEX (a, b, c)能加速WHERE a = 1 AND b = 2,但对WHERE b = 2 AND c = 3完全无用,因为没提供a值,树根本无法起步 -
WHERE a = 1 AND b > 10 AND c = 5中,c不走索引——范围查询(>、BETWEEN)会截断后续列的索引使用 - 即使
EXPLAIN显示type=ref,也要看key_len:若远小于索引总长度,说明只有前几列被实际利用
等值列放前面才能避免范围查询“拦腰截断”
把范围查询列(如时间字段、数值区间)放在前面,等于主动放弃后面所有列的索引能力。真实业务中常见错误是把 created_at 这类高区分度但常带 > 条件的字段塞到索引第一位。
- 错误设计:
INDEX (created_at, user_id),查询WHERE created_at > '2024-01-01' AND user_id = 123→user_id无法走索引 - 正确做法:调换顺序为
INDEX (user_id, created_at),先用user_id = 123快速定位小范围,再在该子集内做时间范围扫描 - 如果同时有
ORDER BY created_at DESC,这个调整还能顺便避免filesort,前提是排序方向与索引一致
高选择性 ≠ 一定要放最前,高频过滤才是优先级
选择性(COUNT(DISTINCT col)/COUNT(*))只是参考,真正关键的是“这个字段是否几乎每次查询都出现”。低选择性字段(如 status 只有 active/inactive)如果高频出现在 WHERE 中,反而应前置——保证索引能被命中,而不是追求单列过滤率。
- 示例:用户表常查
WHERE status = 'active' AND city = 'shanghai' AND created_at > '2024-01-01' -
status选择性低,但 90% 查询都带它 → 放第一列,确保索引可用 -
city和status组合后过滤效果极佳 → 放第二列 -
created_at带范围 → 必须放最后,否则前两列白搭
隐式转换和函数会让整个索引“形同虚设”
哪怕索引顺序完全合理,只要查询里对索引列做了函数或类型转换,B+树就无法直接比对原始值,只能退化成全表扫描。
-
WHERE YEAR(created_at) = 2023→ 索引失效;应改写为WHERE created_at >= '2023-01-01' AND created_at -
WHERE phone = 13800138000(phone是VARCHAR)→ 隐式转数字,索引失效;必须写成WHERE phone = '13800138000' -
WHERE UPPER(name) = 'SMITH'→ 同样失效;如需大小写不敏感,建函数索引(MySQL 8.0+)或用COLLATE utf8mb4_0900_as_cs
最易被忽略的一点:ALTER TABLE 修改联合索引顺序在 MySQL 中本质是删索引再重建,大表上可能锁表数分钟。别指望在线调整,得提前规划好顺序,靠 EXPLAIN 和慢查询日志反复验证真实查询模式。











