执行效率取决于执行计划而非join或子查询语法,现代数据库会自动重写语义等价的查询;真正影响性能的是dependent subquery、中间结果集膨胀和索引缺失。

没有固定答案——执行效率取决于执行计划,而不是你写了 JOIN 还是子查询。 现代数据库(MySQL 8.0+、PostgreSQL、SQL Server)会把语义等价的子查询自动重写为 JOIN,或反之。真正拖慢查询的,往往是 DEPENDENT SUBQUERY、中间结果集膨胀、索引缺失,而不是语法本身。
看到 DEPENDENT SUBQUERY 就该立刻检查
这是 MySQL 和 PostgreSQL 执行计划里最危险的信号,意味着外层每行都触发一次内层查询。比如:
SELECT * FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.status = 'paid');
如果 users 有 50 万行,而 orders 缺少 (user_id, status) 复合索引,这条语句实际会执行约 50 万次独立查询。
- 用
EXPLAIN查看select_type字段:出现DEPENDENT SUBQUERY或DERIVED且 rows 很大,基本等于性能红灯 - 优先改写为
JOIN或LATERAL(PostgreSQL)/APPLY(SQL Server) - 若必须保留子查询,确保内层 WHERE 条件字段有索引,且不带
LIMIT、ORDER BY、GROUP BY——这些会让优化器放弃 semi-join 优化
IN 子查询在 MySQL 5.6+ 常被自动转成 semi-join
只要满足几个条件,WHERE id IN (SELECT user_id FROM orders WHERE amount > 100) 和等价的 JOIN 实际走的是同一条执行路径:
- 子查询不包含
DISTINCT、GROUP BY、LIMIT、ORDER BY - 内层表
orders的user_id字段有索引 - 优化器估算出内层结果集不大(通常
这时 EXPLAIN 中你会看到 type: eq_ref 或 type: ref,和等价 JOIN 完全一致;但一旦加了 SELECT DISTINCT user_id FROM orders GROUP BY user_id,优化器就退化为逐行物化,性能断崖下跌。
JOIN 容易因中间结果集膨胀而变慢
很多人把标量子查询改成 JOIN 后反而更慢,根本原因不是语法,而是行数爆炸:
SELECT u.name, (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) FROM users u;
改写为:
SELECT u.name, COUNT(o.id) FROM users u LEFT JOIN orders o ON u.id = o.id GROUP BY u.id;
如果某个用户有 2000 笔订单,LEFT JOIN 后中间结果先膨胀 2000 倍,再 GROUP BY 聚合,内存压力陡增,还可能触发 Using temporary; Using filesort。
- 仅需判断“是否存在”,用
EXISTS比LEFT JOIN ... IS NOT NULL更轻量——找到第一个匹配就停,不生成中间行 - 要取关联表单个聚合值(如最新时间),标量子查询配合索引可能比 JOIN +
GROUP BY更快 -
DISTINCT不是免费的:它常引发filesort或临时表,尤其在结果集大时,代价可能高于子查询的“重复扫描”
最容易被忽略的点,是中间结果集大小——它不会直接写在 SQL 里,却藏在 EXPLAIN 的 rows 和 Extra 字段中:Using temporary 是膨胀信号,Using index condition 是健康信号。别猜快慢,先看执行计划。










