JOIN 查询慢的主因是中间结果集爆炸或索引未生效,应立即查看EXPLAIN的rows、type、filtered三列:rows骤增说明过滤缺失,type为ALL/index表明右表未走索引,filtered<5%提示WHERE条件未下推;LEFT JOIN的过滤条件须写入ON子句,OR条件需拆为UNION ALL,优先使用覆盖索引和合理驱动表顺序。

JOIN 查询慢,十有八九不是 SQL 写错了,而是中间结果集爆炸或索引根本没用上。直接看 EXPLAIN 里的 rows 和 type,比等它跑完再调优快得多。
怎么看 JOIN 是否失控?盯紧 EXPLAIN 的三列
别等查询超时才想起分析。写完 JOIN 就该跑 EXPLAIN,重点关注:
-
rows值突然跳高(比如从 1 万涨到 200 万):说明某次 JOIN 后数据量失控,常见原因是漏写了时间/状态等过滤字段,比如ON a.id = b.a_id却没加AND a.dt = b.dt -
type是ALL或index:右表没走索引,要么关联字段没建索引,要么类型不一致(如VARCHAR(20)对VARCHAR(50)) -
filtered低于 5%:WHERE 条件几乎没起到剪枝作用,95% 的行被扫出来又丢掉——这类条件本该挪进ON子句里下推
LEFT JOIN 的 WHERE 条件必须塞进 ON
这是最常踩的坑。写成:
SELECT * FROM orders o LEFT JOIN users u ON o.user_id = u.id WHERE u.status = 'active'
MySQL 会先全量 JOIN(包括所有 u.status 为 NULL 的行),再过滤。正确写法是:
SELECT * FROM orders o LEFT JOIN users u ON o.user_id = u.id AND u.status = 'active'
这样优化器才能在 JOIN 阶段就剪掉不匹配的用户行。顺带提醒:WHERE u.col IS NOT NULL 会让 LEFT JOIN 实际退化成 INNER JOIN,业务语义可能已悄悄改变。
OR 条件在 ON 里?立刻拆成 UNION ALL
ON b.x = a.x OR b.y = a.y 这种写法,几乎所有数据库都会放弃索引、触发全表扫描甚至嵌套循环。别纠结改写 WHERE 或加提示,直接拆:
- 确保
b.x和b.y各自有单列索引(或复合索引前导列) - 两个子查询都从左表出发,分别
LEFT JOIN右表的不同别名(如b1、b2) -
SELECT字段顺序、类型、NULL 性必须严格对齐,否则UNION ALL报错 - 绝不加
DISTINCT:除非业务明确要去重,否则UNION ALL是唯一选择
Spark SQL 里曾有作业因 bb.ip = aa.ip1 OR bb.ip = aa.ip2 卡住数天,拆成预处理 + 等值 JOIN 后秒出结果。
覆盖索引和驱动表顺序才是真提速点
回表是随机 IO,10 万行 JOIN 就是 10 万次磁盘寻道。要免回表,就得让索引“覆盖”所有用到的字段:
- 比如查
SELECT user_id, order_time FROM orders JOIN users ON ... WHERE users.status = 'active',就在users上建INDEX(status, id),在orders上建INDEX(user_id, order_time) -
EXPLAIN中看到type=ref且Extra含Using index,才算真正命中覆盖索引 - LEFT JOIN 无法调换左右顺序,但 INNER JOIN 可以。如果左表是千万级订单,右表是几十万用户,优先让过滤性强的小表当驱动表——必要时用
STRAIGHT_JOIN强制
真正卡住的往往不是语法,而是中间结果集膨胀后引发的内存溢出和磁盘 IO;EXPLAIN 里的 rows 值,比最终返回多少行更能暴露问题本质。

















