explain的rows只是优化器基于统计信息的预估,非实际扫描行数;真实扫描行数应查slow log中的rows_examined或performance_schema.events_statements_history。

用 EXPLAIN 看预估扫描行数,但不是实际值
EXPLAIN 显示的是优化器预估的 rows,它基于统计信息估算,并非执行器真正读取的行数。比如 EXPLAIN SELECT * FROM user WHERE name LIKE '%li%' 可能显示 rows=1000,但实际可能全表扫了 5 万行——只要没索引,rows 就只是个粗糙参考。
关键点:这个值不累加、不包含过滤后丢弃的行、也不反映执行器在存储引擎层反复 fetch 的真实 I/O 次数。
查 rows_examined 才是执行器实际读取行数
MySQL 执行器每从存储引擎拿到一行数据(无论是否满足 WHERE 条件),都会把 rows_examined 计数器 +1。这个值才是你真正想看的“读了多少行”。
- 开启慢查询日志(
slow_query_log = ON)并设置long_query_time = 0,所有语句都会记录,日志里就有rows_examined - 用
performance_schema.events_statements_history查最近执行的语句:SELECT SQL_TEXT, ROWS_EXAMINED FROM performance_schema.events_statements_history ORDER BY TIMER_START DESC LIMIT 5;
- 如果只查当前会话,执行完语句后立刻运行:
SHOW STATUS LIKE 'Handler_read%';中的Handler_read_next和Handler_read_rnd_next可辅助判断遍历模式,但不如rows_examined直观
为什么 rows_examined 比 EXPLAIN 的 rows 更重要
执行器实际读取行数直接决定 I/O 开销和 CPU 时间,尤其在没走索引或用了函数/隐式转换时,rows_examined 会远大于 EXPLAIN 预估的 rows。
- 例如
SELECT * FROM order WHERE DATE(create_time) = '2026-01-01':索引失效,rows_examined= 全表行数;而EXPLAIN可能仍按统计误判为小范围扫描 -
ORDER BY+LIMIT场景下,执行器可能读取 10 万行才找到前 10 条满足排序条件的,rows_examined就是 10 万,EXPLAIN却只写rows=10 - 触发器、生成列、JSON 字段解析等操作也会增加实际读取负担,但不会体现在
EXPLAIN的rows里
注意 rows_examined 的统计边界
它只统计执行器向存储引擎发起的「获取下一行」调用次数,不含网络传输、结果集组装、权限校验等开销。
- 对 InnoDB 表,一次
Handler_read_next对应 B+ 树叶子页内一条记录的读取;跳页、回表、二级索引扫描都算在内 - MyISAM 表中,
rows_examined基本等于物理行数扫描量,无 MVCC 开销 - 如果语句被查询缓存命中(MySQL 5.7 及更早),
rows_examined为 0 —— 但 MySQL 8.0 已移除查询缓存,这点不用再担心
真正要定位性能瓶颈,盯住 rows_examined 比死磕 EXPLAIN 的 rows 有用得多。它藏在慢日志和 performance_schema 里,不主动查就永远看不到执行器到底干了什么。











