RangeCheckedForEachRecord 出现在 EXPLAIN 的 Extra 列中,表示 MySQL 无法预先确定索引,需为每行动态评估各索引范围扫描代价,常见于 OR 条件、函数、隐式转换或多表非等值 JOIN 等导致优化器无法静态推断索引适用性的场景。

RangeCheckedForEachRecord 出现在哪里、意味着什么
这个状态出现在 EXPLAIN 的 Extra 列中,不是错误,但说明 MySQL 在执行时无法在查询开始前确定用哪个索引——它得为每一行“现场检查”可用索引的范围扫描代价,再临时选一个。本质是**动态索引选择(dynamic range selection)**,常见于多表 JOIN 或 WHERE 中有 OR、函数、隐式类型转换等导致优化器无法静态推断索引适用性的场景。
哪些写法容易触发 RangeCheckedForEachRecord
它不是 bug,而是优化器在“拿不准”时的保守策略。以下情况高发:
-
WHERE a = ? OR b = ?,且a和b分别有单列索引但无合适联合索引 - JOIN 条件中使用了非等值匹配(如
ON t1.id > t2.ref_id),或关联字段存在隐式转换(比如t1.uid是VARCHAR,t2.uid是INT) - WHERE 子句含函数,如
WHERE YEAR(created_at) = 2024,即使created_at有索引也无法静态评估范围 - 使用了
IN (subquery)且子查询结果集大小不确定,优化器放弃预判索引
怎么确认是不是真影响性能、要不要干预
不能只看 Extra 里有这个词就 panic。关键看实际执行是否慢、rows 是否远超预期、是否有大量临时表或文件排序:
- 用
EXPLAIN FORMAT=JSON查看range_analysis部分,里面会列出它“检查过哪些索引”和各自估算的row_estimate,能看清它为什么犹豫 - 强制指定索引测试:加
USE INDEX (idx_a)或FORCE INDEX (idx_b)对比EXPLAIN的rows和实际执行时间 - 如果表小(RangeCheckedForEachRecord 基本无感;但若出现在日志高频慢查里,大概率是索引设计或写法有问题
真正有效的优化方向
目标不是“消灭这个词”,而是让优化器能静态决策。优先级从高到低:
- 把
OR拆成UNION ALL(确保两支结果无交集),每支可独立走索引 - 为 OR 条件建覆盖性联合索引,例如
(a, b)能同时支撑a = ?和b = ?(注意顺序与区分度) - 避免在索引字段上用函数;改用范围写法,如
WHERE created_at >= '2024-01-01' AND created_at - 检查 JOIN 字段类型是否严格一致,必要时加
CAST或修改 DDL 统一类型
最常被忽略的一点:这个状态往往意味着你正在用多个单列索引去扛本该由一个联合索引解决的问题——先看 WHERE 和 JOIN 涉及的字段组合,再决定建什么索引,而不是反过来。


















