大表join前必须过滤,否则必然崩溃;应将where条件下推至join前或用子查询预筛,避免全量join导致2万亿次比较及oom。

JOIN前不过滤,中间结果集容易爆炸,不是“可能慢”,而是“必然崩”。
大表JOIN没过滤=嵌套循环暴力扫
MySQL默认用Nested Loop Join,外层每行都要去内层全表扫描匹配。
orders(200万行) × users(100万行) → 理论比较次数2万亿次,CPU直接满载、查询超时或OOM。
即使有索引,若驱动表没先筛,被驱动表仍要反复回表或走索引范围扫描,IO压力翻倍。
常见错误现象:EXPLAIN里看到type = ALL或rows列高达百万级,但实际业务只查最近7天订单。
WHERE放JOIN后 vs 子查询里放WHERE
语义和执行路径完全不同: -FROM orders o JOIN users u ON o.user_id = u.id WHERE o.created_at > '2025-01-01':先连再筛,orders全量参与JOIN
- FROM (SELECT * FROM orders WHERE created_at > '2025-01-01') o JOIN users u ON o.user_id = u.id:先筛再连,orders输入行数可能从200万降到5万
关键区别在于优化器能否把WHERE下推到JOIN前。遇到以下情况,下推基本失效:
-
WHERE含OR、LIKE '%xx'、函数如DATE(o.created_at) - 字段类型隐式转换(比如
user_id INT关联VARCHAR字段) - 统计信息过期,
rows预估严重失真
LEFT JOIN里右表过滤必须写进子查询或ON条件
LEFT JOIN orders o ON u.id = o.user_id WHERE o.status = 'PAID'这句看着合理,实则致命:
- 语义上等价于INNER JOIN,所有没订单的用户全被过滤掉
- 性能上迫使数据库先全量JOIN,再扫临时结果集过滤,o.status索引大概率失效
正确写法只有两种:
- 子查询方式:
LEFT JOIN (SELECT * FROM orders WHERE status = 'PAID') o ON u.id = o.user_id -
ON内联过滤:LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'PAID'(注意:AND不能换成WHERE)
后者对索引友好性取决于复合索引设计——(user_id, status)比单列status索引更有效。
过滤后JOIN的边界感最容易被忽略
很多人以为“加了WHERE就等于提前过滤”,但没意识到: -SELECT *在子查询里会拖慢剪枝速度,应只选必要字段(尤其避免TEXT或BLOB列)
- 多表JOIN时,每个表的过滤时机要独立判断,不能只滤主表;比如地区表虽小,但加WHERE region_code IN ('CN', 'US')仍值得提前筛
- STRAIGHT_JOIN能强制顺序,但前提是EXPLAIN确认驱动表rows确实最小——否则反而更慢











