优化器选错JOIN顺序主因是rows估算严重失准,根源在于采样页数过少(默认20页)及SQL写法不当(如函数索引、缺失最左前缀、隐式转换等),导致成本计算错误。

Rows不准直接导致优化器选错JOIN顺序
优化器不是靠猜,而是靠rows估算值算成本。它把“驱动表预估行数 × 被驱动表单次访问成本”当核心指标。如果rows低估了100倍(比如预估5000,实际61万),优化器就会认为Nested Loop很便宜,果断选它——结果内层被扫61万次,哪怕每次只走主键,IO和CPU也扛不住。
常见现象:EXPLAIN里某张表type=ref但rows=1,实际执行时Rows_examined高达几十万;或者Extra出现Using join buffer (Block Nested Loop),说明已退化为全量内存匹配。
采样页数太小是MySQL里Rows失准的头号原因
innodb_stats_persistent_sample_pages默认值20,意味着优化器只随机看20个数据页就推断整张表的分布。碰到数据倾斜(比如95%订单status='done')、长尾字段(如user_type只有'vip'和'normal'两种值),这20页很可能全是'normal',导致对'vip'的查询严重低估行数。
- 千万行以下、分布均匀:保持默认20即可
- 1000万–5亿行 + 低基数字段(如
status、type):设为100~200 - 超大表(>5亿)或高度倾斜:可临时设到400,但必须在业务低峰期操作
- MySQL 8.0+且已建直方图:优先用
CREATE STATISTICS,别盲目调这个参数
即使采样再准,这些写法也会让Rows彻底失效
优化器根本不会用统计信息,因为条件本身让它“没法用”:
-
WHERE YEAR(create_time) = 2024:函数导致索引失效,优化器退化为全表扫描估算 - 联合索引
(a,b,c),但写WHERE b = 1:缺失最左前缀,该路径无对应统计 - 分区表跨多个分区查询:优化器叠加各分区估算值,而非探测实际匹配分区
-
LEFT JOIN orders ON users.id = orders.user_id WHERE orders.status = 'paid':右表非空条件把LEFT变成INNER,还可能误导优化器误判选择率
看到rows=1但扫了50万行,大概率不是采样问题
这时候先别急着调innodb_stats_persistent_sample_pages。真正关键的是确认:JOIN字段类型是否一致(比如bigint vs text)、有没有隐式转换、ON条件里是否用了函数、右表过滤有没有错放WHERE里。这些问题会让统计信息完全绕过,调再多样本页数也没用。

















