handler_read_*等指标是存储引擎层物理操作计数器,反映索引定位、顺序/随机读写等底层动作次数,需结合sql类型、执行计划及explain交叉分析,单看数值易误判。

用 SHOW STATUS 查看 Handler 统计值
MySQL 的 Handler 层不直接暴露“扫描行数”这个指标,但会累计各类底层操作次数,其中 Handler_read_next、Handler_read_prev、Handler_read_rnd、Handler_read_rnd_next 等最能反映实际数据行扫描行为。执行 SQL 前后对比这些值,差值就是该语句在引擎层触达的行数。
实操建议:
- 先执行
FLUSH STATUS重置会话级统计(避免受之前查询干扰) - 再执行目标 SQL(确保是单独一条,不带其他逻辑)
- 立即运行
SHOW STATUS LIKE 'Handler%',重点关注Handler_read_*项变化 - 注意:该方式只对当前会话有效,且无法区分索引扫描 vs. 全表扫描,需结合
EXPLAIN判断访问类型
为什么 rows_examined 在慢日志里更可靠
当开启慢查询日志(slow_query_log = ON)并设置 log_queries_not_using_indexes = OFF 时,MySQL 会在每条记录末尾写入 # Rows_examined: N。这个值由服务器层在调用存储引擎接口后累加返回的行数,比 Handler_read_* 更贴近“用户视角的扫描行数”——它已过滤掉引擎内部跳过的无效行(比如被 MVCC 版本过滤掉的行)。
常见误区:
-
Rows_examined不等于EXPLAIN中的rows字段:后者是优化器预估,前者是真实执行反馈 - 如果语句没进慢日志(执行快于
long_query_time),就看不到这个值;可临时调低阈值或强制写入(SET SESSION long_query_time = 0) - 使用
SELECT SQL_NO_CACHE ...避免查询缓存干扰统计
performance_schema.table_io_waits_summary_by_index_usage 的局限性
这个表记录了每个索引的 I/O 等待次数,但它的 COUNT_READ 是“读请求次数”,不是“读取的行数”。一次 Handler_read_next 可能只返回一行,也可能因缓冲区批量读取返回多行——MySQL 并不把每次引擎调用返回的行数逐条上报到这里。
所以它适合判断“哪个索引被频繁访问”,但不能替代 Rows_examined 或 Handler_read_* 差值来算扫描行数。
使用前提:
- 必须启用
performance_schema且相关消费者(如events_waits_current)已打开 - 表中数据是累积值,需要手动做前后快照 diff,且粒度是“索引级”,无法定位到单条 SQL
- 对于 InnoDB 的聚簇索引扫描,
INDEX_NAME显示为PRIMARY,但无法区分是覆盖扫描还是回表扫描
真正想看“每条 SQL 扫了多少行”,优先抓 information_schema.PROFILING
虽然该功能在 MySQL 8.0 中已被标记为 deprecated,但在 5.7 和部分 8.0 环境仍可用,且是唯一能在语句级直接看到 Rows_examined 实际值的方式。
步骤很简单:
- 执行
SET profiling = 1 - 运行你的 SQL
- 执行
SHOW PROFILES找到 Query_ID - 执行
SHOW PROFILE ROWS_SENT, ROWS_EXAMINED FOR QUERY N—— 这里的Rows_examined就是你要的数字
注意:PROFILING 是会话级、内存驻留的,开多了会影响性能;生产环境慎用,调试完记得 SET profiling = 0。
真正容易被忽略的是:InnoDB 的 Rows_examined 包含所有被引擎返回给服务器层的行,哪怕最后被 WHERE 条件全过滤掉,只要进了 server 层,就算一次“examined”。这和你直觉里的“有效扫描”可能有偏差。











