复合索引必须遵循最左前缀原则,因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',MySQL 不知道该去哪个user_id分支找,只能全表扫描
WHERE 条件顺序不影响索引匹配,但缺失最左列就彻底失效
写成 WHERE status = 'paid' AND user_id = 100 或 WHERE user_id = 100 AND status = 'paid' 效果一样——MySQL 优化器会自动调整顺序。但关键点是:
- 必须包含
user_id(最左列),否则整个索引不可用 -
WHERE user_id = 100 AND create_time > '2025-01-01'只能用到user_id,create_time因中间跳过status而无法走索引 -
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作为范围终点,之后无列可用 - 注意:
LIKE 'abc%'是范围(前缀匹配),但LIKE '%abc'或LIKE '%ab%'直接导致最左列失效
真正容易被忽略的是:即使你写了所有列,只要最左列没出现在 WHERE 条件中(哪怕只在 ORDER BY 或 GROUP BY 里),索引就大概率不会被选中;而一旦最左列被函数包裹(如 WHERE YEAR(create_time) = 2025),整条复合索引也直接失效——这不是配置问题,是 B+ 树没法对变换后的值建立有序路径。











