MySQL放弃range扫描选择ALL是优化器基于成本模型的主动决策,因预估回表随机I/O开销大于顺序扫描聚簇索引,常见于高行数占比、统计失真、范围过宽或复合索引范围列位置不当等情况。

MySQL 从 type=range 退化为 type=ALL,不是 bug,也不是配置错误,而是优化器基于成本模型做出的主动选择——它算出来“走索引再回表”比“直接顺序扫一遍”更慢。
为什么优化器会放弃 range 扫描?
核心是成本估算失衡。MySQL 用统计信息预估满足 WHERE 条件的行数(rows),再结合 I/O 特性算总成本:
- 当
rows占表比例超过约 20%–30%,回表带来的随机 I/O 开销会远超全表扫描的顺序 I/O -
innodb_stats_persistent_sample_pages值过小,导致统计信息不准,误判数据分布 - 范围条件本身太宽,比如
created_at > '2020-01-01'在十年老表里实际命中 80% 行,优化器自然弃索引 - 复合索引中范围列位置不对,例如索引是
(a, b, c),但查询是WHERE a = 1 AND c > 100,c无法有效利用索引下推
EXPLAIN 显示 type=range,但执行很慢怎么办?
这说明优化器没退化,但实际性能差,常见于高回表代价场景:
安装、修复、更新或打包一个基于 Python 和 PyMySQL 实现的 Windows `mysql` 命令。适用于用户希望在 PowerShell/CMD 中输入 `mysql` 时重定向到 Python 实现、需要交互式输入 MySQL 连接信息、需要同步到当前用户本地 bin 目录,或需要将该能力封装成可公开复用的 Codex skill。
- 二级索引覆盖字段少,每匹配一行都要回主键索引取完整行 → 检查是否能加
Covering Index,把 SELECT 字段都包含进索引 - 索引区分度低,如
status只有 3 个值,range扫描仍要查大量行 → 改用IN或拆分查询,或考虑 Skip Scan(MySQL 8.0+) - 并发高时 buffer pool 热点失效,物理 I/O 暴增 → 观察
Handler_read_rnd_next是否飙升,而非只看 EXPLAIN
什么情况下强制走索引反而更糟?
用 FORCE INDEX 或 USE INDEX 并不总是救星:
- 对小表(如
- 索引列存在大量 NULL,且查询含
IS NOT NULL,MySQL 可能误估非空行数,强制后实际扫描行数爆炸 - WHERE 中混用函数,如
WHERE DATE(create_time) = '2026-11-05',即使强制索引也无效——优化器根本不会考虑该索引
真正关键的不是“怎么让它走索引”,而是“它为什么不想走”。盯着 rows、filtered、实际执行时间三者关系,比盲目加索引或 Hint 更有效。统计信息不准、数据倾斜、I/O 模式变化,这些底层变量一动,执行计划就可能翻脸不认人。










