EXPLAIN的rows只是预估,不执行语句、不读真实数据页,基于过时统计信息估算;真正反映实际扫描行数的是EXPLAIN ANALYZE的actual rows,需MySQL≥8.0.18、SELECT语句且有PROCESS权限。

EXPLAIN的rows只是预估,不是实测
它基于过时或低精度的统计信息(比如INFORMATION_SCHEMA.STATISTICS)做成本估算,不执行语句、不读真实数据页。哪怕你刚删了90%的数据,只要没触发自动重采样(默认变更约10%才触发),rows仍可能显示“扫描10万行”,而实际只返回3条。
常见误导现象包括:
-
WHERE user_id = '123'(user_id是INT)——隐式类型转换导致索引失效,EXPLAIN仍显示key有值、rows=200,但实际全表扫 - 复合索引
(a,b,c),查询写WHERE b = 1 AND c = 2——EXPLAIN中key为NULL或possible_keys有值但未命中,rows直接按全表估算 -
WHERE YEAR(create_time) = 2024——函数使索引失效,rows退化为表总行数
真正能暴露差距的只有EXPLAIN ANALYZE
它强制真实执行一次查询,并返回actual rows、actual time和loops。这是唯一能告诉你“优化器猜得准不准”的方式。
必须满足三个条件,否则会静默退化为普通EXPLAIN:
- MySQL ≥
8.0.18(低于报ERROR 1064) - 语句必须是
SELECT(不支持INSERT ... SELECT子查询、UPDATE/DELETE) - 用户需有
PROCESS权限(否则返回空或权限错误)
示例输出中这一行最关键:-> Index range scan on orders using idx_user_status (user_id=1001, status IN (1,2,5)) (cost=12.50 rows=85) (actual time=0.042..1.28 rows=72 loops=1)
这里的rows=72才是真实扫描的索引行数,不是预估的85。
统计信息不准,ANALYZE TABLE是最快补救手段
默认采样页数innodb_stats_persistent_sample_pages=20,对大表或数据倾斜严重(比如status=1占95%)的场景极易失真。
手动刷新统计信息:
- 直接运行
ANALYZE TABLE your_table_name - 如需更高精度,先临时调高采样:
SET SESSION innodb_stats_persistent_sample_pages = 100,再ANALYZE TABLE,完后设回默认 - 确认持久化已启用:
SHOW VARIABLES LIKE 'innodb_stats_persistent',值必须为ON才会落盘
但注意:即使ANALYZE了,若WHERE含函数、跨分区查询、或只用联合索引非最左列,rows仍可能严重偏离。
实际执行和计划不一致,往往藏在运行时细节里
EXPLAIN不考虑锁等待、buffer pool冷热、磁盘排序、并发争抢。所以常出现:
-
EXPLAIN显示Using index,但慢日志里tmp_disk_tables > 0——说明sort_buffer_size不够,被迫落盘排序 -
EXPLAIN没显示Using temporary,但performance_schema.events_statements_history_long里timer_wait远高于lock_time——瓶颈在CPU或I/O,不是锁 -
Rows_examined远大于Rows_sent(来自慢日志)——典型回表多、JOIN顺序错或索引未覆盖
真正影响性能的,从来不是EXPLAIN里那行rows数字,而是slow_query_log里的Rows_examined和tmp_disk_tables——它们才是执行后留下的真实痕迹。


















