rows不准不是navicat问题,而是mysql优化器基于采样统计的固有估算机制所致;其本质是“预计扫描行数”,非实际读取量或结果集大小,失真主因是函数使用、隐式转换、非最左前缀等导致统计被绕过,需优先排查这些硬性失效点而非盲目调参。

rows不准不是Navicat的问题,是MySQL优化器的统计机制决定的
MySQL的EXPLAIN中rows列从来就不是精确值,它是优化器基于采样页估算出来的“可能扫描行数”,不是实际读取行数,也不是结果集大小。哪怕你看到rows=1但实际扫了50万行,这不一定是bug,而是优化器压根没用上统计信息——优先排查硬性失效点,而不是一上来就调参数。
先查为什么统计被绕过:函数、隐式转换、非最左前缀
这些写法会让优化器直接放弃索引统计,退化为粗略估算(比如全表行数除以2),此时调大采样页数毫无意义:
-
WHERE YEAR(create_time) = 2024或WHERE UPPER(name) = 'A':索引字段被函数包裹,统计失效 -
WHERE id = '123'(id是INT)或WHERE code = 123(code是VARCHAR):隐式类型转换导致索引无法匹配 - 联合索引
(a,b,c),却写WHERE b = 1:缺失最左前缀,该查询路径无对应统计 -
LEFT JOIN ... WHERE right_table.status = 'paid':把LEFT JOIN逻辑转成了INNER JOIN,还可能干扰选择率估算
确认是否真需要调innodb_stats_persistent_sample_pages
只有在数据倾斜严重(如status字段95%是'done')、表超千万行、且已排除上述硬性失效后,才考虑调参:
- 千万级以下、分布均匀的表:
innodb_stats_persistent_sample_pages保持默认20即可 - 1000万–5亿行 + 低基数字段(如
type、status):SET GLOBAL innodb_stats_persistent_sample_pages = 100或200 - 必须配合
ANALYZE TABLE your_table才生效;光改参数不执行ANALYZE等于没动 - 确认
innodb_stats_persistent = ON(MySQL 5.6+ 默认开启),否则统计不落盘,重启即丢 - 超大表(>5亿行)设到
400要谨慎:ANALYZE TABLE期间加意向锁,可能阻塞写入,务必选低峰期
别只盯着rows,用Handler_read_*看真实IO
rows只是成本估算输入项,真正反映执行开销的是运行时状态变量:
- 执行完查询后,立刻运行
SHOW SESSION STATUS LIKE 'Handler_read_%' - 重点关注
Handler_read_next(索引遍历次数)和Handler_read_rnd_next(回表次数),它们比rows更接近真实扫描量 - 如果
rows=1但Handler_read_next高达几十万,说明驱动表估算严重失真,JOIN顺序可能已错 - MySQL 8.0+ 可建直方图:
ANALYZE TABLE t UPDATE HISTOGRAM ON status,对=、IN类谓词有效,但对BETWEEN或函数条件无效
真正难的不是让rows变准,而是判断它在哪种条件下彻底失效——比如看到rows跳变剧烈,先看EXPLAIN FORMAT=JSON里的used_columns和key_parts,再查有没有函数或类型转换。统计永远只是近似值,而写法才是可控的确定性因素。











