mysql执行计划未走索引是优化器基于成本选择全表扫描,主因包括小表、低区分度、统计信息过期、索引列被函数/计算/隐式转换、联合索引未按最左前缀匹配、or条件含非索引列。

MySQL执行计划没走索引,不是“漏了”或“bug”,而是优化器明确选择了全表扫描——它算过成本,觉得那样更快。
EXPLAIN 显示 type=ALL,但 WHERE 条件明明能用索引
这是最典型的误判场景。优化器看到 WHERE age > 25,即使 age 有索引,也可能选 type=ALL。原因不是索引坏了,而是:
- 表太小(比如只有几百行),全表扫描比走索引+回表还省 IO
-
age区分度极低(比如 90% 的人 age 都在 20–35),用索引要回表查大量行,优化器预估成本更高 - 统计信息过期:
ANALYZE TABLE users没跑过,优化器基于陈旧数据做决策
验证方式:运行 SELECT COUNT(*) FROM users WHERE age > 25,如果返回值占总行数比例 > ~20%,优化器大概率放弃索引。
WHERE 中对索引列用了函数或计算
只要索引列出现在函数里、表达式里、或被隐式转换,索引就失效——因为 B+Tree 存的是原始值,不是计算结果。
-
WHERE YEAR(create_time) = 2023→ 失效;应改写为WHERE create_time >= '2023-01-01' AND create_time -
WHERE price * 1.1 > 100→ 失效;应改为WHERE price > 100 / 1.1 -
WHERE mobile = 13800138000(mobile是VARCHAR)→ 隐式转数字,失效;必须写成WHERE mobile = '13800138000'
注意:ORDER BY 或 GROUP BY 中对索引列做函数,同样导致无法利用索引排序/分组。
联合索引没按最左前缀匹配
索引 KEY idx_name_age (name, age) 不是“name 和 age 都有索引”,而是一棵先按 name 排、再按 age 排的树。所以:
-
WHERE name = '张三'→ 可用 -
WHERE name = '张三' AND age > 25→name用索引定位,age > 25做索引内范围扫描(仍高效) -
WHERE age = 25→ 完全失效,age在树里是无序的 -
WHERE name LIKE '%三' AND age = 25→name失效(前导 %),整个联合索引作废
别指望优化器“聪明地拆解条件”——它只认定义顺序。
OR 条件中混入了非索引列
WHERE name = '李四' OR age = 25 很危险:哪怕 name 有索引、age 没索引,MySQL 默认放弃所有索引,直接全表扫描。
- 不是因为语法错,而是优化器认为:满足
name = '李四'的行少,但满足age = 25的行多,合并结果集代价太高 - 修复方式不是加索引,而是拆成两个查询:
SELECT ... WHERE name = '李四' UNION ALL SELECT ... WHERE age = 25 - 或者给
age单独建索引,触发index_merge(但不保证一定用,且有额外开销)
真正容易被忽略的是:哪怕你只查 SELECT id FROM ...,只要 OR 涉及非索引列,依然可能全表扫——优化器决策发生在执行计划生成阶段,和你最终要哪些字段无关。











