必须加有效的on条件且关联字段不能有重复值或null,否则即使写了join也会因逻辑冗余导致结果膨胀;例如订单表某user_id出现3次、用户表该id出现2次,join后生成6行;需用group by+having查重复、is null查null、避免on中用函数或类型转换,并优先用exists替代left join+where非空判断。

必须加有效的 ON 条件,且关联字段不能有重复值或 NULL;否则即使写了 JOIN,照样会膨胀。
为什么加了 ON 还会出笛卡尔积
常见错觉是“只要写了 ON 就安全”,但实际中 ON a.id = b.a_id 无法阻止膨胀,当 a.id 或 b.a_id 存在重复值时,一对多关系会被无约束放大。例如:订单表里某 user_id 出现 3 次,用户表里该 id 又出现 2 次,JOIN 后就生成 6 行——这不是语法错误,是逻辑冗余。
排查方法:
- 用
SELECT user_id, COUNT(*) FROM orders GROUP BY user_id HAVING COUNT(*) > 1查左表重复 - 用
SELECT a_id, COUNT(*) FROM b GROUP BY a_id HAVING COUNT(*) > 1查右表重复 - 用
SELECT COUNT(*) FROM b WHERE a_id IS NULL看是否含 NULL(尤其影响 LEFT JOIN 行为)
ON 里别写函数,否则索引失效、优化器乱选连接算法
像 ON UPPER(a.code) = UPPER(b.code) 这种写法会让数据库放弃使用 code 列上的索引,导致优化器倾向选择哈希连接而非嵌套循环,中间结果集极易暴涨。
正确做法:
- 提前把函数逻辑下推到数据写入侧(如统一存小写)
- 或建函数索引(MySQL 8.0+ 支持
CREATE INDEX idx_code_lower ON b ((LOWER(code)))) - 避免在 ON 中做类型转换,如
ON a.id = CAST(b.a_id AS SIGNED)
用 EXISTS 替代 LEFT JOIN + WHERE 非空判断
当只需确认“主表某行在从表是否存在匹配”,写成 LEFT JOIN ... WHERE b.id IS NOT NULL 是危险的:数据库会先完成全量连接再过滤,中间结果已膨胀。
更优写法:
SELECT o.* FROM orders o WHERE EXISTS (SELECT 1 FROM order_items i WHERE i.order_id = o.order_id)- EXISTS 是半连接,找到第一个匹配即停,不生成中间行
- 如果后续还要取
i.product_name等字段,才必须回到 JOIN,但此时务必确保order_items(order_id)有索引
多表聚合时,先各自聚合再 JOIN
查“每个客户总金额 + 最近下单时间”,若直接三表 JOIN 后 GROUP BY,会把所有订单明细拉进内存,数量级远超必要。
应拆成两步:
- 先聚合订单表:
SELECT user_id, SUM(amount) AS total, MAX(create_time) AS last_time FROM orders GROUP BY user_id - 再和用户表 JOIN:
SELECT u.name, t.total, t.last_time FROM users u JOIN (...) t ON u.id = t.user_id - 避免中间结果集超过物理内存,Spark SQL 场景下还能防止 OOM 和严重数据倾斜
真正难的不是写出能跑的 SQL,而是意识到:JOIN 的每一行输出,都对应着底层数据的一次逻辑配对。配对规则模糊、字段质量差、索引缺失——任何一环松动,都会让结果集悄悄翻倍。别依赖 EXPLAIN 的“rows”估算,上线前一定要用真实数据量 COUNT 验证中间结果规模。











