explain执行计划与实际运行不一致主因是优化器基于陈旧统计信息、隐式类型转换或查询重写生成次优计划;需用analyze table更新统计、检查隐式转换、对比handler_read%等真实指标而非仅依赖explain估算。

为什么 EXPLAIN 显示的执行计划和实际运行不一致
MySQL 的执行计划缓存(Query Plan Cache)在 8.0.22+ 后被彻底移除,但查询重写(如改写 IN 为 EXISTS、加提示 /*+ USE_INDEX() */)仍可能触发优化器重新生成执行计划——而这个“新计划”未必更优,尤其当统计信息陈旧或隐式类型转换干扰时。
常见错误现象:EXPLAIN 看起来走了索引,但 SELECT 执行慢;或者重写后 EXPLAIN 成本更低,但实际耗时翻倍。
- 检查是否触发了隐式转换:比如
WHERE user_id = '123'(user_id是INT),会导致索引失效,EXPLAIN中type变成ALL或index - 确认统计信息是否过期:
ANALYZE TABLE t_name;比依赖自动采样更可靠,尤其对大表或频繁 DML 的场景 - 避免依赖
EXPLAIN的rows估算值:它不反映真实扫描行数,仅基于统计模型;用SHOW STATUS LIKE 'Handler_read%';对比重写前后更准
重写 IN 子查询时,EXISTS 不一定更快
很多人默认把 IN (SELECT ...) 改成 EXISTS 就能提升性能,但 MySQL 优化器对相关子查询的处理逻辑在不同版本差异很大,尤其涉及 NULL 值或外层过滤条件时。
使用场景:主表小、子查询表大,且子查询有合适索引;但如果外层 WHERE 条件未下推,EXISTS 可能变成嵌套循环,反而比物化 IN 更差。
- 先看原始
IN是否被物化:执行EXPLAIN FORMAT=JSON,找"materialized_from_subquery"字段;存在说明已优化,强行改EXISTS可能倒退 -
EXISTS必须确保子查询关联字段有索引,否则变成对子表全扫描;而IN物化后是哈希查找,O(1) 平均复杂度 - 测试时禁用查询缓存:
SELECT SQL_NO_CACHE ...,避免因缓存掩盖重写效果
FORCE INDEX 在重写后突然失效的原因
显式指定索引看似可控,但重写查询后,MySQL 可能因谓词变化、隐式转换或优化器成本模型调整,直接忽略 FORCE INDEX——这不是 bug,而是优化器判定“强制走索引代价高于全表扫”。
典型表现:SELECT ... FROM t FORCE INDEX (idx_a) WHERE a = 1 AND b > NOW() 重写为 WHERE DATE(b) = '2024-01-01' 后,idx_a 失效,因为 DATE() 阻止索引使用。
- 用
EXPLAIN确认key字段是否真用了目标索引;如果显示NULL,说明强制失败 - 避免在
FORCE INDEX字段上做任何函数操作、类型转换或表达式计算 - 若必须函数过滤,优先建函数索引:
CREATE INDEX idx_b_date ON t ((DATE(b)));(MySQL 8.0.13+)
评估重写影响时,绕不开的三个真实指标
光看 EXPLAIN 的 cost 或 rows 容易误判。真正要盯的是执行时的资源消耗和响应行为。
-
Handler_read_next:升序扫描索引的次数,飙升说明索引范围过大或排序未命中索引 -
Sort_merge_passes:归并排序次数,> 0 表示排序无法在内存完成,磁盘临时文件拖慢速度 - 慢日志中的
Rows_examined和Rows_sent比值:如果远大于 1(如 1000:1),说明大量无效行被扫描,重写应聚焦减少WHERE过滤前的扫描量
复杂点在于:这些指标受并发负载、buffer pool 状态、甚至 SSD 随机读延迟影响。单次对比没意义,至少跑 3 轮,取中位数;并且确保两次执行之间没有 FLUSH TABLES 或重启,否则 buffer pool 清空会扭曲结果。











