MySQL优化器按成本估算选择JOIN驱动表顺序,EXPLAIN中table列从下到上为实际执行顺序,小表指过滤后预估行数少的表,非物理行数;LEFT JOIN限制重排但右表强过滤+索引可提前;无索引关联字段会迫使优化器放弃重排。

MySQL优化器改变JOIN驱动表顺序,不是bug,也不是随意行为,而是基于成本估算后做出的主动选择——它只关心“哪条路径预估执行代价最低”,不关心你写的先后。
EXPLAIN显示的table顺序就是实际驱动顺序
很多人误以为SQL里先写的表就是驱动表,但EXPLAIN输出中table列从上到下,对应的是MySQL物理执行的**从下往上**顺序。也就是说,最后一行的表才是最先被扫描的驱动表。
- 例如
EXPLAIN返回两行,table列为orders(第1行)、users(第2行),那真实执行是先查users,再用它的结果去匹配orders - 哪怕你写的是
FROM orders LEFT JOIN users,只要users过滤后只剩几行且有索引,优化器就敢把它提为驱动表 -
EXPLAIN FORMAT=TREE(MySQL 8.0+)能更直观看到嵌套层级和谁在最外层
优化器选驱动表只看rows × filtered,不是总行数
所谓“小表驱动大表”,这个“小”指的是WHERE或ON条件过滤后的预估行数,不是SHOW TABLE STATUS里的Rows。优化器靠采样估算,不是靠直方图(MySQL 8.0仍无原生直方图支持)。
安装、修复、更新或打包一个基于 Python 和 PyMySQL 实现的 Windows `mysql` 命令。适用于用户希望在 PowerShell/CMD 中输入 `mysql` 时重定向到 Python 实现、需要交互式输入 MySQL 连接信息、需要同步到当前用户本地 bin 目录,或需要将该能力封装成可公开复用的 Codex skill。
-
rows=1000000但filtered=0.001%→ 实际参与JOIN约10行,它就是事实上的“小表” -
rows=1000但filtered=100%→ 全表都要扫,它反而可能成为瓶颈 - 如果
filtered接近100%且rows很大,大概率说明条件没走索引:检查字段类型是否一致、有没有函数包裹、是否存在隐式转换
LEFT JOIN不是铁律,但会限制重排自由度
LEFT JOIN语义要求左表数据必须全部保留,所以优化器不能把左表“筛掉”,但它可以物化右表、再反向查找——前提是右表过滤强、索引好、成本更低。
- 当左表无有效过滤(如
WHERE为空或只对主键范围查询),而右表有高选择性条件(如status = 'done'且该字段有索引),优化器很可能把右表提前 - 但如果右表条件写在
WHERE而非ON里,比如LEFT JOIN users ON orders.user_id = users.id WHERE users.status = 'active',优化器会误判为“必须先加载users全表”,放弃重排机会 - 正确写法应是
ON ... AND users.status = 'active',这样优化器才清楚右表可早筛
没索引的关联字段会让优化器“不敢动”
一旦某张表的JOIN字段(如t2.t1_id)没索引,优化器基本放弃把它当被驱动表——因为意味着每次都要全表扫描+大量间隙锁,成本爆炸。这时它可能被迫按你写的顺序走,哪怕逻辑上不合理。
- 这种“妥协式执行”常出现在老系统迁移后:表结构没变,但业务SQL突然变慢,一查发现关联字段丢了索引
-
EXPLAIN中type为ALL、key为NULL、Extra含Using join buffer,基本可断定被驱动表没走索引 - 加索引后,再看
EXPLAIN,常会发现驱动表顺序变了,且rows显著下降
真正难的不是看懂EXPLAIN,而是理解为什么优化器认为某条路径更便宜——它依赖的统计信息是采样得来的,不一定准;它做的成本模型也不包含你业务里的热点分布。所以别迷信“理论上该这么走”,关键还是用EXPLAIN验证,用ANALYZE TABLE更新统计信息,必要时用STRAIGHT_JOIN强制顺序(仅限调试或确定场景)。


















