<p>Handlerread* 状态变量反映存储引擎层实际执行的行级操作次数,是接近真实物理扫描量的调用计数,而非SQL层返回行数或优化器估算值;它统计引擎接口调用次数,不区分是否命中缓冲池,也不等同于磁盘I/O次数。</p>

Handler_read_* 状态变量到底反映什么
Handler_read_* 系统状态变量(如 Handler_read_next、Handler_read_key、Handler_read_rnd)记录的是存储引擎层实际执行的行级操作次数,不是 SQL 层的“返回行数”,也不是优化器预估的扫描行数。它接近真实物理扫描量,但要注意:它统计的是引擎接口调用次数,不是磁盘 I/O 次数,也不区分是否命中缓冲池。
为什么不能只看 Handler_read_rnd 判断全表扫描
Handler_read_rnd 表示通过行位置(rowid)随机读取某一行,常见于排序后回表、或 ORDER BY RAND() 场景;但它**不等于全表扫描**。真正反映全表/范围扫描强度的是:Handler_read_next(按索引顺序向后读)、Handler_read_prev(向前读)、Handler_read_first(定位索引首条)。例如:
- 全表扫描(无索引)→ 大量
Handler_read_next(InnoDB 实际走聚簇索引的顺序遍历) - 索引范围扫描 →
Handler_read_key(1次定位起点)+ 大量Handler_read_next - 使用覆盖索引且无需回表 →
Handler_read_key+Handler_read_next,但Handler_read_rnd= 0
如何精准关联一次 SQL 和它的 Handler 变化
必须用会话级状态清零 + 单语句执行,避免其他连接干扰。操作步骤如下:
- 在目标会话中执行
FLUSH STATUS(仅重置当前连接的SHOW STATUS计数) - 立即执行你的目标 SQL(不要加 EXPLAIN,要真实执行)
- 立刻执行
SHOW STATUS LIKE 'Handler_%',筛选出变化非零的项 - 重点关注
Handler_read_key(索引查找次数)、Handler_read_next(索引/主键顺序扫描行数)、Handler_read_rnd(回表或随机读行数)
注意:Handler_read_first 增加 1 次通常意味着扫描了整个索引(哪怕只查 1 行),是全索引扫描的强信号。
Handler 统计和 EXPLAIN rows 的差异在哪
EXPLAIN 中的 rows 是优化器基于统计信息估算的“可能扫描行数”,而 Handler_read_* 是真实发生的引擎调用次数。两者常不一致,原因包括:
- 统计信息过期(
ANALYZE TABLE未及时运行)→EXPLAIN rows偏离严重 - 查询含函数、隐式转换、OR 条件 → 优化器估算失效,但 Handler 数值仍真实
- 使用了 ICP(Index Condition Pushdown)→
EXPLAIN rows显示“索引过滤前”的行数,而Handler_read_next只计真正被引擎逐行判断的次数 - 多表 JOIN 时,
EXPLAIN的rows是单表估算,Handler 是各表实际交互总和
所以当发现 EXPLAIN rows 很小但响应慢,务必查 Handler —— 很可能是估算崩了,实际扫了几万行才找到结果。


















