每个JOIN后必须写ON,否则默认CROSS JOIN导致笛卡尔积;USING可替代ON但要求字段名一致;多表连接需每对相邻表都有ON条件;WHERE不能替代ON做连接约束;一对多关联应先聚合再JOIN。

每个JOIN后面必须写ON,不能省略
不写ON子句,数据库不会报错,但会默认执行CROSS JOIN——也就是笛卡尔积。哪怕只漏一个ON,中间结果就可能爆炸。
- 错误写法:
SELECT * FROM orders LEFT JOIN customers;(缺ON) - 正确写法:
SELECT * FROM orders LEFT JOIN customers ON orders.customer_id = customers.id; -
USING (customer_id)可以替代ON,但要求两表字段名完全一致且存在;它不是“省条件”的捷径 - MySQL严格模式下会直接报错,但旧版或宽松配置可能静默退化为笛卡尔积,更危险
多表连接时检查“桥接链”是否断裂
三张及以上表连接,不能只保证首尾有关系,每一对相邻表都得有业务上成立的ON条件。跳过中间实体强行关联,数据库只能暴力匹配。
- 错误链路:
orders JOIN order_items ON orders.id = order_items.order_id JOIN products;(漏了order_items.product_id = products.id) - 后果:先算
order_items × products,再连orders,中间行数可能翻几十倍 - 验证方法:对每个
JOIN后加LIMIT 5查中间结果行数,若某步后突增数十倍,大概率断链了 - 子查询作表时也一样——它变成临时表后,仍需和外层有明确
ON,不能靠“内部已过滤”蒙混过关
别让WHERE代替ON做连接约束
WHERE在连接完成之后才执行,它无法减少连接过程中的中间膨胀。用它“补救”缺失的ON,只是把爆炸延迟到后期,性能更差。
- 错误写法:
SELECT * FROM a JOIN b WHERE a.id = b.a_id;(逗号语法+WHERE,等价于无约束CROSS JOIN后再过滤) - 正确写法:
SELECT * FROM a JOIN b ON a.id = b.a_id; -
LEFT JOIN ... ON ... AND right.status = 'active'是合法的,但语义是“左表全保留,右表只取active”,若后续又在WHERE里写right.amount > 0,就会意外干掉所有NULL行 - 想过滤右表,优先考虑
INNER JOIN;只想判断是否存在,改用EXISTS更安全
一对多关联导致行数放大,得先聚合再JOIN
主表1行、明细表A有3行、明细表B有5行,直接LEFT JOIN两个明细表,结果就是1×3×5=15行——这不是语法错,是逻辑失控。
- 典型场景:
orders→order_items→payments,三者都按order_id关联,但order_items和payments彼此无关 - 低效写法:
SELECT o.*, i.qty, p.amount FROM orders o LEFT JOIN order_items i ON ... LEFT JOIN payments p ON ... - 推荐写法:把
order_items和payments分别按order_id聚合后再JOIN,例如(SELECT order_id, SUM(qty) AS total_qty FROM order_items GROUP BY order_id) i - 如果只需判断存在性(如“该订单是否有支付记录”),直接用
EXISTS,不生成中间行
真正难防的不是漏写ON,而是写了但字段重复、NULL多、类型不一致或索引缺失——这些会让ON形同虚设,执行计划里rows_examined_per_scan暴增才是第一信号。

















