子查询适合条件依赖或单值判断,JOIN适合多表关联取值;相关子查询因重复执行而慢,可用EXISTS或JOIN优化;驱动表应选小表并建索引;聚合过滤、TOP-N等场景宜保留子查询。

子查询和JOIN是SQL复杂查询的核心,面试中常考性能差异、适用场景及优化技巧。关键不是死记语法,而是理解执行逻辑和数据分布对效率的影响。
什么时候该用子查询,而不是JOIN?
子查询适合表达“条件依赖”或“单值判断”,尤其在WHERE或HAVING中作为过滤依据。比如查“工资高于部门平均工资的员工”,用标量子查询更直观:
- 推荐写法(清晰+可读性高): SELECT name, salary FROM emp WHERE salary > (SELECT AVG(salary) FROM emp e2 WHERE e2.dept_id = emp.dept_id);
- 不建议强行JOIN替代: 若为每个员工关联一次部门均值,需先GROUP BY再JOIN,代码冗长且易出错;若用窗口函数虽高效,但部分老版本MySQL不支持,子查询反而兼容性更好。
相关子查询为什么慢?怎么优化?
相关子查询会为外层每一行重复执行内层查询,时间复杂度接近O(n×m)。常见陷阱是用IN + 子查询检查存在性,如:
- 低效写法: SELECT * FROM orders WHERE customer_id IN (SELECT id FROM customers WHERE city = 'Beijing');
- 优化方向: 改为LEFT JOIN或EXISTS。EXISTS通常更快,因为找到一条即停止;JOIN则适合还需取客户信息的场景。
- 注意NULL安全: IN遇到子查询结果含NULL时可能整体返回空——这是易被忽略的语义坑,EXISTS无此问题。
JOIN多表时,驱动表选择影响巨大
数据库一般以小表为驱动表(outer table),逐行匹配大表(inner table)。如果没索引,大表会被全扫描多次,性能断崖式下降。
- 看执行计划(EXPLAIN)确认驱动顺序: MySQL中type=ALL的表通常是被驱动表;Extra列出现“Using join buffer”说明走块嵌套循环,需警惕。
- 强制调整(慎用): STRAIGHT_JOIN可指定左表为驱动表,但仅当统计信息不准或优化器误判时考虑,日常应优先建索引。
- 索引建议: JOIN字段必须有索引,且类型一致(如INT vs VARCHAR)、无函数包装(如JOIN ON UPPER(a.name)=UPPER(b.name)会失效)。
子查询转JOIN的边界在哪?
并非所有子查询都该转JOIN。以下情况保留子查询更合理:
- 聚合后过滤:如“找出订单数超过5的客户”,用HAVING比先JOIN再GROUP BY更直接;
- TOP-N每组:MySQL 8.0前不支持PARTITION BY + LIMIT,常用相关子查询实现“每个部门薪资前3的员工”;
- 逻辑简洁性优先:当子查询只用于判断真假(如EXISTS),改JOIN反而增加冗余字段和去重成本。
真正决定性能的是数据量、索引覆盖和执行路径,不是语法形式本身。动手前先EXPLAIN,看rows和type;上线前用真实数据集压测,比背技巧管用得多。










