复合索引遵循最左前缀原则,因b+树按列分层排序,跳过左列会导致无法利用索引的有序性;范围查询会截断右侧列索引;非最左列查询、函数操作、or连接等均使索引失效;覆盖索引也需满足最左前缀才能避免回表。

复合索引的B+树结构决定了它只能从左开始切分数据
MySQL的InnoDB引擎所有索引底层都是B+树,而复合索引的排序不是“每列独立排序”,而是严格分层:先按第一列全局排序,相同值再按第二列排序,再相同才按第三列排序。这意味着索引条目在磁盘上是连续存储的(city, age, name)三元组,而不是三个分开的有序列表。
所以当你查WHERE age = 25时,数据库无法跳过city直接定位age——因为age=25的记录可能散落在不同city分组里,物理上根本不相邻。它只能全表扫描或全索引扫描,失去B+树的二分查找优势。
范围查询会截断索引匹配链
一旦查询中出现>、、<code>BETWEEN或LIKE 'abc%'这类范围条件,MySQL就无法继续向右匹配后续列。比如索引是idx_user_status_ctime(user_id, status, create_time):
-
WHERE user_id = 100 AND status = 'paid'→ 能用到全部两列 -
WHERE user_id = 100 AND status > 'created'→ 只能用到user_id和status,create_time索引失效 -
WHERE user_id = 100 AND create_time > '2025-01-01'→create_time列完全无法走索引(中间跳过了status)
不满足最左前缀时,优化器可能干脆放弃走索引
即使表上有复合索引,只要WHERE条件没包含最左列,MySQL优化器大概率选择全表扫描,而不是“部分使用”索引。这不是bug,而是成本权衡:用索引扫描再回表过滤,可能比直接全表扫描更慢。
常见误判场景:
- 写
WHERE status = 'paid' AND create_time > '2025-01-01',以为能用idx_user_status_ctime→ 实际type是ALL - 对索引首列做函数操作:
WHERE YEAR(create_time) = 2025→ 即使create_time在索引最右,整条索引也失效 - 用
OR连接非最左列:WHERE user_id = 100 OR status = 'paid'→ 索引通常无法生效
覆盖索引依赖最左前缀才能真正免去回表
覆盖索引的价值在于“只查索引不回主键树”,但前提是查询字段和WHERE条件共同构成的列组合,必须满足最左前缀。例如索引idx_name_age_position(name, age, position):
-
SELECT name, age FROM employees WHERE name = 'Bill'→ 覆盖,不回表 -
SELECT position FROM employees WHERE name = 'Bill'→ 不覆盖(position不在最左前缀路径上),仍需回表取值 -
SELECT * FROM employees WHERE name = 'Bill' AND age = 30→ 即使*要所有字段,WHERE部分仍只触发前两列索引,后续字段靠回表
最左前缀不是语法限制,而是B+树物理结构强加的访问契约。跳过它,等于要求数据库在一本按“省→市→区”排序的电话簿里,只凭“区名”快速翻出所有人——它做不到,也没法假装做到。











