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使用磁盘临时表











