mysql的join执行顺序由优化器基于统计信息决定,并非按sql书写顺序;left join中on与where语义不同,错放条件会导致连接退化为inner join;驱动表应小且过滤性强,被驱动表on字段需有高效索引。

MySQL 的 JOIN 执行顺序由优化器决定,不是按 SQL 书写顺序
写 select * FROM A JOIN B ON ... JOIN C ON ...,不代表 MySQL 先连 A 和 B、再连 C。优化器会基于统计信息(如行数、索引选择性)重排表连接顺序,目标是让中间结果集最小。你可以用 EXPLAIN 看实际顺序:EXPLAIN SELECT ... 输出的 table 列从上到下就是物理执行顺序。
常见误解是“左连接就一定先查左边”,其实 LEFT JOIN 仅保证左表全量保留,但优化器仍可能把右表提前物化或下推条件——只要语义等价,它就敢改。
- 强制固定顺序?加
STRAIGHT_JOIN(仅限INNER JOIN场景),例如:SELECT STRAIGHT_JOIN * FROM A JOIN B ON ... JOIN C ON ... - 查看真实计划时,注意
rows和filtered列:前者是预估扫描行数,后者是条件过滤后剩余比例,两者相乘接近实际参与 join 的行数 - 没走索引的 ON 条件会导致嵌套循环(NLJ)退化为全表扫描级联,比如
A.id = B.non_indexed_col可能让 B 被扫几十次
ON 和 WHERE 对 LEFT JOIN 的影响完全不同
ON 是在 join 过程中决定“哪些右表行能匹配左表行”,而 WHERE 是在 join 完成后对整行结果过滤。对 LEFT JOIN 来说,把本该写在 ON 里的条件错放 WHERE,会直接让左连接退化为内连接。
例如:SELECT * FROM orders LEFT JOIN users ON orders.user_id = users.id WHERE users.status = 'active' —— 这条语句会排除所有 users 为 NULL 的订单(即无用户信息的订单),因为 WHERE 在 join 后执行,NULL = 'active' 为 false。
- 正确写法应是:
SELECT * FROM orders LEFT JOIN users ON orders.user_id = users.id AND users.status = 'active' - 如果是多表 LEFT JOIN,每个
ON只约束紧邻右侧的表,不跨表生效 - 复合条件如
ON a.x = b.y AND b.z > 10中,b.z > 10属于“join 条件”,会在 join 阶段过滤 b 表,而非最后扫全量再筛
驱动表与被驱动表的选择直接影响性能
MySQL 的 Nested Loop Join(NLJ)里,先查的叫驱动表(outer table),后查的叫被驱动表(inner table)。驱动表应尽量小且有高选择性过滤条件;被驱动表必须对 ON 字段有高效索引,否则每行驱动都触发一次全表扫描。
例如:10 万行的 log 表 LEFT JOIN 100 行的 config 表,即使 config 没索引也问题不大;但如果反过来,config 做驱动表去连百万行 log,而 log.event_type 没索引,就会执行 100 × 百万次磁盘 I/O。
- 用
EXPLAIN看type列:被驱动表显示ALL就危险;理想是ref、eq_ref或range - 被驱动表的 ON 字段必须是索引的最左前缀,比如索引是
(a, b),那么ON t1.a = t2.a可用,但ON t1.b = t2.b不可用 - 临时表场景下(如子查询物化),驱动表也可能变成内存临时表,此时要注意
tmp_table_size和max_heap_table_size是否够用,否则会落盘变慢
JOIN 缓存(join_buffer)只用于非驱动表的关联字段匹配
join_buffer 是线程级内存缓冲区,用于加速被驱动表的查找。它不缓存驱动表数据,也不缓存被驱动表整行,只缓存 ON 条件中涉及的字段(如 ON a.id = b.a_id,则只缓存 b.a_id)。
这个缓冲区大小由 join_buffer_size 控制,默认仅 256KB。当被驱动表很大、又无法走索引时,增大它可减少磁盘 I/O,但过大会挤占其他操作内存。
- 只对未使用索引的 join 有效(
type = ALL或index);一旦走了索引,join_buffer就不参与 - 每个被驱动表单独分配一个 buffer,三表 join 且都无索引时,最多消耗 3 ×
join_buffer_size - 线上调优优先建索引,而不是盲目调大
join_buffer_size;buffer 再大也救不了没索引的 NLJ
实际执行路径远比语法结构复杂,尤其当统计信息不准、索引失效或存在隐式类型转换时,优化器很容易选错驱动表或忽略可用索引。别只信 EXPLAIN 的预估,务必用 EXPLAIN format=json 查看 used_columns、key_length 和 rows_examined_per_scan 这些细节。