EXPLAIN显示type=ALL是优化器基于成本模型主动放弃索引,因预估索引+回表总开销大于顺序读聚簇索引;常见原因包括低Cardinality、函数操作、非覆盖查询及统计信息失真。

EXPLAIN 显示 type=ALL 就是优化器主动放弃索引
这不是配置错误或 bug,而是 MySQL 8.0 的成本模型算出来:走索引 + 回表的总开销 > 直接顺序读聚簇索引。哪怕 WHERE status = 'paid' 这种简单条件,只要优化器预估要回表几十万次,它就宁愿扫整张表。
关键判断信号包括:key 字段为空(不是没索引,是这次没选)、rows 接近总行数、filtered 极低(比如 filtered=1.00),说明该条件几乎不筛数据。
Cardinality 失真会让优化器直接跳过索引
比如 status 列只有 3 个值,总行数 500 万,但 SHOW INDEX FROM orders 查到的 Cardinality 是 3 —— 优化器会认为查其中一种值要随机回表上百万次,I/O 开销远超顺序扫描。
- 验证方式:
SHOW INDEX FROM orders WHERE Key_name = 'idx_status' - 临时修复:
ANALYZE TABLE orders(注意锁表现象,避开高峰) - 长期方案:开启持久化统计信息,
SET GLOBAL innodb_stats_persistent = ON+innodb_stats_auto_recalc = ON - 别单独为低区分度字段建单列索引;优先把它放在联合索引后位,例如
INDEX(user_id, status)
MySQL 8.0 对“老写法”的代价惩罚更重
这些在 5.7 可能蒙混过关的 SQL,在 8.0 更容易触发全表扫描:
-
WHERE DATE(create_time) = '2026-09-01'→ 函数导致索引失效,且 8.0 对函数计算的 cost 惩罚更高;应改写为create_time >= '2026-09-01' AND create_time -
WHERE status IN (SELECT status FROM config WHERE active = 1)→ 子查询无索引时,8.0 更倾向放弃整个外层索引路径 -
ORDER BY create_time DESC LIMIT 20配合INDEX(user_id, create_time)→ 若user_id过滤性差(比如命中 80% 行),8.0 会认为“先取全部再排序”比走索引更便宜
覆盖索引没建好,强制走索引反而更慢
FORCE INDEX 不解决根本问题。它只是跳过成本估算,不改变物理执行代价。典型翻车场景:
- 小表(比如
1000行以内),全表扫描就是一次顺序 I/O,而强制索引要先读二级索引页 + 再回主键查数据,反而多两次随机读 - 索引碎片严重(
DATA_FREE大、页利用率低),导致实际物理读放大 - 语句是
SELECT *或ORDER BY id,但索引里没包含id或其他 SELECT 列,仍需大量回表
真正要盯住的是 optimizer_trace 输出里的 cost_for_plan 和 considered_plan,而不是只看 EXPLAIN 的 key 是否非空。很多“看起来该走索引”的语句,trace 里会明确写着 "chosen": false, "cause": "cost" —— 这才是最该花时间的地方。











