LEFT JOIN 比 INNER JOIN 慢因需保留左表全部记录并补 NULL,无法提前剪枝,常触发嵌套循环与 join buffer;右表 ON 字段须有匹配索引,WHERE 过滤右表会转为 INNER JOIN;EXPLAIN 中 key 为空、rows 过大、type 为 ALL/index、Extra 含 Using temporary/filesort 均提示性能风险;驱动表应选 WHERE 过滤后行数更少者,非物理大小;禁用 ON 中函数、多层嵌套 JOIN 及 TEXT/BLOB 参与 JOIN。

为什么LEFT JOIN 比 INNER JOIN 更慢?
因为 LEFT JOIN 必须保留左表全部记录,即使右表没有匹配项也要补 NULL,导致 MySQL 无法像 INNER JOIN 那样提前剪枝。执行计划里常看到Using where; Using join buffer,说明它在用缓存做嵌套循环,数据量一大就卡。
- 确认是否真需要 LEFT JOIN:很多业务场景其实能改成 INNER JOIN,比如查“用户及其订单”,若只要已下单用户,就别用 LEFT
- 右表的
ON字段必须有索引,且类型、字符集、排序规则要和左表完全一致,否则索引失效 - 避免在 LEFT JOIN 的右表条件中写
WHERE子句过滤右表字段(如WHERE o.status = 'paid'),这会把 LEFT JOIN 逻辑转成 INNER JOIN,还可能让优化器误判执行顺序
如何判断 JOIN 是否走了索引?
直接看 EXPLAIN 输出里的 key 和rows列:key为空或为 NULL,基本没走索引;rows 值远大于实际匹配行数,说明扫描范围过大。
- 对多表 JOIN,
EXPLAIN的table顺序就是 MySQL 实际连接顺序,优化器不一定会按 SQL 写的顺序执行,所以STRAIGHT_JOIN有时反而更可控 -
type列要是ref或eq_ref才健康,ALL或index意味着全表 / 全索引扫描 - 如果
Extra里出现Using temporary或Using filesort,说明 JOIN 后还触发了临时表或排序,得拆查询或加覆盖索引
小表驱动大表到底怎么选?
所谓“小表”不是指物理大小,而是 JOIN 过程中 ** 参与循环的行数更少的那张表 **。MySQL 默认用驱动表(outer table)去逐行探测被驱动表(inner table),所以驱动表越小,总探测次数越少。
- 用
EXPLAIN看rows列预估行数,选预估结果更小的作为左表(INNER JOIN)或主表(LEFT JOIN) - 别只看
COUNT(*),要考虑 WHERE 条件过滤后的实际结果集大小。比如users WHERE status = 'active'可能只有 1 万行,而orders有 500 万行,但orders WHERE created_at > '2024-01-01'只剩 2 万行——这时候后者更适合作驱动表 - 用
STRAIGHT_JOIN强制顺序时,确保自己算得准,否则可能比优化器还差
哪些 JOIN 写法会直接拖垮性能?
这些写法看着简洁,实则极易触发全表扫描或临时表,线上务必规避:
SELECT u.name, COUNT(o.id) FROM users u LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id;
问题在于:没加 WHERE 限制用户范围,users全表被加载进内存做 GROUP BY,orders也全表关联。正确做法是先缩小驱动表范围:
SELECT u.name, IFNULL(cnt, 0) AS order_count FROM users u LEFT JOIN (SELECT user_id, COUNT(*) AS cnt FROM orders WHERE created_at >= '2024-01-01' GROUP BY user_id ) o ON u.id = o.user_id WHERE u.status = 'active';
- 禁止在 ON 条件里用函数或表达式(如
ON u.id = CAST(o.user_id AS SIGNED)),索引必然失效 - 避免多层嵌套 JOIN(超过 4 张表),优先考虑应用层分步查询 + 内存关联
- TEXT/BLOB 字段尽量不在 JOIN 条件或 SELECT 里出现,它们会迫使 MySQL 使用磁盘临时表