应优先用INNER JOIN或EXISTS替代WHERE IN子查询,避免全表扫描和NULL陷阱;LEFT JOIN过滤条件须放ON子句;UNION ALL适合日志类垂直拼接;JOIN顺序影响性能需结合EXPLAIN分析。
子查询嵌套在 WHERE 中常被误用为 JOIN 的替代
当需要根据另一张表的条件过滤主表数据时,WHERE ... IN (SELECT ...) 看似简洁,但容易触发全表扫描和重复执行。比如查“所有有订单的用户”,写成 SELECT * FROM users WHERE id IN (SELECT user_id FROM orders),MySQL 可能对每个 users 行都重跑一次子查询(尤其未加 DISTINCT 或索引时)。
更稳妥的做法是改用 INNER JOIN 或 EXISTS:
SELECT DISTINCT u.* FROM users u INNER JOIN orders o ON u.id = o.user_id;
或:
SELECT * FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);
-
EXISTS在找到第一条匹配即停止,适合“是否存在”类判断 -
IN子查询若返回NULL,整条WHERE判断会变成UNKNOWN,导致结果为空——这是最常被忽略的逻辑陷阱 -
JOIN更利于利用索引,且执行计划通常更稳定
LEFT JOIN 后 WHERE 条件写错位置会导致隐式转 INNER JOIN
想查“所有用户及其订单数(含 0)”,却写了:
SELECT u.name, COUNT(o.id) FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.status = 'paid' GROUP BY u.id;
结果只返回有已支付订单的用户——因为 WHERE o.status = 'paid' 把 o 为 NULL 的行全过滤掉了,LEFT JOIN 形同虚设。
正确做法是把过滤条件移到 ON 子句:
SELECT u.name, COUNT(o.id) FROM users u LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'paid' GROUP BY u.id;
-
ON控制“如何连接”,WHERE控制“连接后如何筛选” - 多表
LEFT JOIN时,后续表的过滤条件也必须放在对应ON中,否则会逐级收缩左表结果 - 用
EXPLAIN查看type是否为ALL,可快速识别是否意外丢失了外连接语义
JOIN 顺序影响性能,但 MySQL 8.0+ 优化器通常能自动调整
早期版本中,写 FROM large_table JOIN small_table 比反过来快,因为驱动表(large_table)决定循环次数。但现在 MySQL 优化器多数情况下会重排顺序,前提是各表有可用索引且统计信息准确。