mysql执行SQL时join操作的执行顺序_mysql多表关联执行解析

来源:这里教程网 时间:2026-02-28 20:51:03 作者:

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
这些细节。

相关推荐