联合索引生效依赖b+树的最左前缀结构,非最左列无法定位数据;范围查询会截断右侧列的索引使用;条件顺序不影响,但最左列缺失或被函数包裹则索引失效。

联合索引不是“必须遵循”最左前缀原则才能生效,而是它的底层 B+ 树结构天然决定了:不从最左列开始,就根本没法定位数据——这不是 MySQL 的限制,是物理存储决定的必然结果。
联合索引的 B+ 树结构决定了查找起点只能是最左列
比如索引 idx_user_status_ctime(user_id, status, create_time),B+ 树实际按三层排序:先全局按 user_id 升序;user_id 相同的再按 status 升序;前两者都相同时,才按 create_time 升序。
这意味着:status 只在每个 user_id 分支内有序,跨 user_id 就完全无序;create_time 更只在 user_id + status 完全匹配的子块里连续。
-
WHERE status = 'paid'→ 没有user_id,MySQL 不知道该进哪个分支找,只能全表扫描 -
WHERE user_id = 100 AND create_time > '2025-01-01'→create_time中间跳过了status,无法定位,索引只用到user_id -
WHERE user_id = 100 AND status IN ('paid', 'pending') AND create_time = '2025-01-01'→status是等值、create_time是等值,三列全命中
范围查询会刚性截断右侧列的索引使用
一旦某列用了 >、、<code>BETWEEN 或 LIKE 'abc%',右边所有列就不再参与索引查找,仅用于回表后过滤。
-
WHERE user_id = 100 AND status > 'canceled' AND create_time = '2025-01-01'→ 索引只走到status,create_time不走索引查找 -
WHERE user_id = 100 AND status = 'paid' AND create_time > '2025-01-01'→user_id和status用于精确定位,create_time作为范围终点,之后无列可用 -
WHERE status = 'done' AND create_time > '2025-01-01'(索引为(status, create_time, user_id))→ 前两列可用,user_id断掉,不是因为值分布,而是 B+ 树无法在create_time >对应的多个不连续子树中继续二分
WHERE 条件顺序不影响,但缺失或函数包裹最左列就彻底失效
优化器会自动重排条件顺序,所以 WHERE status = 'paid' AND user_id = 100 和 WHERE user_id = 100 AND status = 'paid' 效果一样。真正关键的是:
- 最左列没出现在
WHERE条件中(哪怕只在ORDER BY或GROUP BY里),索引大概率不会被选中 -
WHERE YEAR(create_time) = 2025→ 函数导致整条复合索引失效,B+ 树没法对变换后的值建立有序路径 -
EXPLAIN中的key_len值能直接反映实际使用的字节数:若只显示user_id长度,说明status和create_time没参与索引定位
最容易被忽略的点是:你写了所有列,但只要最左列没出现在 WHERE 中,或者被函数/表达式包裹,索引就基本等于没建——这不是慢,是根本没用上。











