rows值远超预期说明mysql预估扫描行数过多,主因是缺失合适索引、索引失效(如函数/隐式转换)、统计信息过期或索引选择性低。

EXPLAIN 输出里 rows 值远超预期,说明什么
rows 是 MySQL 估算的扫描行数,不是返回行数。它直接反映 CPU 消耗强度:值越大,意味着 MySQL 要逐行读取、过滤、比较的数据越多。如果 rows 接近表总行数(比如千万级表扫了 980 万行),基本等于告诉你是全表扫描在烧 CPU。
常见误导点:rows 为 1 不代表快——可能是走索引但后续排序/分组又触发 Using filesort 或 Using temporary,照样高 CPU。
- 查表总行数用
SELECT COUNT(*) FROM table_name,别只信SHOW TABLE STATUS的Rows字段(InnoDB 下不准) - 如果
EXPLAIN中rows明显大于 WHERE 条件能筛出的实际数据量,优先检查索引是否覆盖查询条件 -
rows值波动大?说明统计信息过期,执行ANALYZE TABLE table_name更新
type=ALL 就是红灯,但 type=range 也不一定安全
type=ALL 表示全表扫描,必须处理。但更隐蔽的是 type=range 却仍扫几十万行——比如对一个没加索引的 status 字段建了单列索引,而该字段区分度极低(95% 都是 'PAID'),MySQL 认为“用了索引”,实际仍要遍历大量索引节点+回表,CPU 毫不手软。
关键看组合:type 得配合 rows 和 key 一起读。哪怕 type=ref,若 rows 是 50 万,也得优化。
- 区分度低的字段(如 status、is_deleted)单独建索引意义很小,考虑改成联合索引的前置列
-
type=range时,检查key_len是否符合预期——比如索引是(a,b,c),但key_len只显示 a 的长度,说明 b/c 没用上 - 用
FORCE INDEX强制走某索引后rows暴降?说明优化器选错了执行计划,可能需要OPTIMIZE TABLE或更新统计信息
Extra 里出现 Using filesort 或 Using temporary,CPU 就危险了
Using filesort 不代表真在磁盘排序,而是 MySQL 需要额外分配内存做排序;Using temporary 表示创建了内部临时表(哪怕在内存)。这两者都绕不开 CPU 密集计算,尤其当 ORDER BY 或 GROUP BY 字段没索引,或索引无法覆盖排序需求时。
典型陷阱:ORDER BY create_time DESC LIMIT 20 看似简单,但如果 create_time 没索引,或 WHERE 条件已让索引失效,MySQL 就得先捞出所有匹配行再排序——rows 可能几万,CPU 直接拉满。
- 排序字段必须进索引,且顺序要和
ORDER BY一致(ASC/DESC 匹配) - 联合索引中,
WHERE条件列必须是前缀,排序列必须紧跟其后,否则索引无法用于排序 -
tmp_table_size和max_heap_table_size设太小会导致本该内存完成的排序/临时表被迫落盘,加剧 CPU 和 IO 开销
为什么加了索引,EXPLAIN 还显示 key=NULL
key=NULL 表示优化器完全没用索引,常见原因不是“没建”,而是“建了但废了”。最常踩的坑是隐式类型转换和函数操作:
-
WHERE user_id = '123'(user_id是INT)→ 触发隐式转换,索引失效 -
WHERE DATE(create_time) = '2026-01-01'→ 对索引列用函数,索引失效 -
WHERE status IN ('PAID', 'SHIPPED') AND amount > 100,但索引是(amount, status)→ 顺序反了,status无法走索引 - 字符集不一致(如表用
utf8mb4,连接用latin1)也会导致索引失效,但错误不报,只默默全表扫
真正难缠的是“看起来走了索引,实际没走”——比如 EXPLAIN 显示 key=idx_status,但 rows=999999,这时候得怀疑是不是字段区分度太低,或者统计信息严重偏差。











