mysql优化器按成本最低的left-deep连接顺序执行join,explain的table列从上到下即实际驱动顺序;驱动表应为过滤后结果集最小的表,且被驱动表join字段须有匹配索引,统计信息准确是前提。

MySQL优化器不按你写的表顺序执行JOIN,而是穷举left-deep连接顺序,选成本最低的那条路径——但这个“成本”依赖统计信息是否准确、索引是否可用、条件写在哪。
EXPLAIN显示的table顺序就是最终驱动顺序
你看到EXPLAIN输出中table列从上到下,就是物理执行时的驱动顺序:第一行是第一个被全量扫描的表(驱动表),后续每一行都基于前序结果去匹配。这不是书写顺序,也不是LEFT JOIN语法强制的左表优先,而是优化器算出来的最优路径。
关键点:
- 它只考虑left-deep树结构,比如
A → (B → C)合法,(A → B) → C也合法,但(A → C) → B这类非left-deep会被跳过 -
rows × filtered是估算实际参与JOIN的行数,比单看rows更有参考价值 - 如果某张表
rows很大但filtered接近100%,大概率是ON或WHERE里用了函数、隐式转换,导致索引失效,优化器被迫全扫
LEFT JOIN会限制重排自由度,但不是绝对禁止
LEFT JOIN语义要求左表必须保留全部行,所以优化器通常不敢把右表提前当驱动表——但这不是铁律。如果左表本身没有效过滤(比如没WHERE、没高选择性条件),而右表有极强筛选能力(如status = 'paid'预估只剩2行),优化器仍可能物化右表,再反向匹配左表。
常见陷阱:
- 把本该在
ON里的右表过滤条件挪到WHERE,比如LEFT JOIN orders ON u.id = o.user_id WHERE o.status = 'shipped',语义已退化为INNER JOIN,但优化器仍按外连接逻辑执行:先全扫users,再逐行查orders,最后才过滤status - 想靠
STRAIGHT_JOIN强行固定顺序?可以,但它会禁用所有重排逻辑,一旦中间表数据分布突变,性能可能断崖下跌
被驱动表的JOIN字段必须有可用索引,且类型/字符集完全一致
哪怕优化器选对了驱动表,如果被驱动表的ON字段没走索引,NLJ就退化成全表嵌套循环,性能直接崩盘。
实操要点:
- 索引必须是单列索引,或至少是复合索引的最左前缀;
INDEX(created_at, user_id)对ON ... = b.user_id无效 - 用
SHOW CREATE TABLE比对两边字段的DATA_TYPE、CHARACTER_SET和COLLATION,常见坑是utf8mb4vslatin1、BIGINTvsINT - 避免在ON里写
CAST()、CONVERT()或函数包裹,它们会让索引彻底失效
小表驱动大表 ≠ 物理小表,而是“过滤后结果集最小”的表
驱动表选错,性能差10倍起步。但“小表”不是指SELECT COUNT(*)出来的行数,而是经过WHERE条件过滤后、真正参与JOIN的行数。
比如一张千万级订单表,加了WHERE created_at > '2026-09-01'后只剩500行,它就比一个10万行但没过滤的用户表更适合作为驱动表。
所以:
- 高选择性WHERE条件一定要“贴着驱动表”写,别堆在最后
- 多表JOIN时,优化器可能因某张表缺失关键索引而放弃重排,老老实实按SQL字面顺序执行——这时如果大表在左,中间结果集爆炸,QPS直接归零
- 用
EXPLAIN FORMAT=TREE(MySQL 8.0+)看真实嵌套结构,比传统EXPLAIN更直观
最常被忽略的一点:优化器的成本估算高度依赖ANALYZE TABLE后的统计信息。表数据批量导入或删改后没及时更新统计,它就算再努力,也是拿错误输入算出错误结果。











