极简工具箱

免费在线工具 · 本地计算 · 无需上传

MySQL 索引为什么没走:最左前缀与六个失效场景

明明建了索引,`EXPLAIN` 里却是 `type: ALL` 全表扫描。索引失效的原因就那么几个,按清单过一遍基本都能找到。

广告位(百度联盟)

一、先理解索引长什么样

InnoDB 的主键索引是聚簇索引:叶子节点直接存整行数据,所以按主键查最快。二级索引的叶子节点存的是「索引列 + 主键值」,查到主键后还要回表再取整行。

这解释了两件事:

二、最左前缀原则

联合索引 (a, b, c) 相当于按 a 排序、a 相同按 b 排、再按 c 排。所以:

查询条件能否用索引
a = ?能
a = ? AND b = ?能
a = ? AND b = ? AND c = ?能
b = ?不能(跳过最左列)
a = ? AND c = ?部分能用(只用到 a)
a = ? AND b > ? AND c = ?a、b 能用,c 用不上(范围之后失效)

最后一行是重点:范围查询之后的列无法继续用于索引定位(但可用于覆盖索引,避免回表)。

三、六个失效场景

1. 在索引列上做运算或函数

-- 失效
WHERE DATE(created_at) = '2026-11-03'
WHERE id + 1 = 100

-- 改写为范围
WHERE created_at >= '2026-11-03 00:00:00' AND created_at < '2026-11-04 00:00:00'

2. 隐式类型转换

-- phone 是 varchar,传数字会触发全表转换
WHERE phone = 13800138000      -- 失效
WHERE phone = '13800138000'    -- 正常

反过来,int 列传字符串通常仍能用索引,但 varchar 列传数字几乎必然失效。统一加引号最省事。

3. 前导模糊匹配

WHERE name LIKE '%张'      -- 失效
WHERE name LIKE '张%'      -- 能用

需要全文检索就用全文索引或搜索引擎,别指望 B+ 树。

4. OR 连接了无索引列

WHERE a = 1 OR b = 2   -- b 无索引 → 整体退化为全表扫描

改成 UNION ALL 两个各自能走索引的查询,往往更快。

5. 联合索引顺序不对

建了 (user_id, status) 却按 status 单独查,用不上。要么调整索引顺序,要么补一个 (status) 的索引。

6. 选择性太低

status 只有 0/1 两个值,索引扫一半数据还不如全表。优化器会主动放弃索引,这是正常行为,不是 bug。低区分度列适合放在联合索引的后面,而不是单独建索引。

四、覆盖索引

-- 索引 (user_id, created_at)
SELECT user_id, created_at FROM orders WHERE user_id = 7;

查询的列全在索引里,Extra 会显示 Using index,不需要回表,速度快一个量级。所以「少用 SELECT *」不只是规范问题,直接影响能否覆盖。

五、怎么验证

EXPLAIN SELECT ...;

重点看三列:type(ALL 是全表扫)、key(实际用了哪个索引)、rows(预估扫描行数)。详细解读见 EXPLAIN 怎么看。

六、索引不是越多越好

建议:单表索引控制在 5 个以内,优先建联合索引覆盖多个查询,定期用 sys.schema_unused_indexes 找从未使用的索引并清理。

七、实操顺序

  1. 找出慢查询(见 慢查询定位)。
  2. EXPLAIN 看是否全表扫、扫描行数多少。
  3. 按上面的清单核对失效原因。
  4. 调整 SQL 或补索引,再 EXPLAIN 验证。
  5. 上线后观察写入性能是否受影响。

小结

联合索引遵守最左前缀,范围查询之后的列失效;列上做函数运算、隐式类型转换、前导 % 都会失效;低区分度列别单独建索引;用 EXPLAIN 的 type 和 key 验证。

本文相关工具(本地计算,无需上传)

更多使用教程