相关子查询每次执行都依赖外层行,典型特征是子查询引用外层列(如t2.id = t1.id),导致外层每行触发一次内层执行,最坏达n×m复杂度;而join是集合级操作,优化器可选驱动表、用索引或哈希连接,只需扫描必要数据。

相关子查询每次执行都依赖外层行
相关子查询(Correlated Subquery)的典型特征是:子查询里引用了外层查询的列,比如 WHERE t2.id = t1.id。这意味着它不是一次性执行完再拿结果,而是外层每扫描一行,就触发一次子查询执行。性能上容易变成“N × M”复杂度——如果外层返回 1 万行,内层表有 10 万行,最坏可能做 10 亿次比较。
常见写法如:
SELECT name FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.status = 'paid');
这里 u.id 是外层列,EXISTS 每次都用当前 u.id 去查 orders 表。
- 优化器无法提前物化结果,基本不会转成 semi-join
- 即使
orders.user_id有索引,仍要为外层每一行做一次索引查找 - EXPLAIN 里会显示
DEPENDENT SUBQUERY或UNCACHEABLE SUBQUERY
JOIN 是集合级操作,一次完成匹配
JOIN 把两张表当作整体来处理,优化器可选择驱动表、使用哈希连接或排序合并,整个过程只扫描必要数据。只要关联字段有索引,就能避免逐行探查。
等价改写示例:
SELECT u.name FROM users u INNER JOIN orders o ON o.user_id = u.id WHERE o.status = 'paid';
注意:WHERE 条件放在 JOIN 后,和相关子查询里的逻辑位置不同——它过滤的是连接后的结果集,而非驱动内层查询。
- 若只需判断存在性,
INNER JOIN可能返回重复行(一个用户多笔已支付订单),需加DISTINCT或用LEFT JOIN + IS NOT NULL - 若外层表很小(如只查 10 个用户),
JOIN的批量处理优势更明显 - MySQL 8.0+ 支持
Hash Join,对大表关联比嵌套循环快得多
EXISTS 和 IN 在相关场景下也不能混用
看似都是“查是否存在”,但行为差异很大:
-
EXISTS是布尔短路:找到第一个匹配就停,适合“是否存在”类判断 -
IN (SELECT ...)如果子查询含NULL,整个表达式结果为UNKNOWN,该行被过滤掉——这是容易忽略的语义陷阱 -
IN子查询若未加WHERE过滤,可能返回大量值,导致临时表或内存溢出 - 当子查询结果为空时,
EXISTS返回false,IN返回false,但= ANY(...)会返回NULL
别只看写法,先看执行计划
同一个业务逻辑,EXISTS 和 JOIN 的性能谁更好,最终得看 EXPLAIN 输出。关键观察点:
- 是否有
DEPENDENT SUBQUERY—— 有就是相关子查询,大概率慢 -
type是否为eq_ref或ref—— 表示走了索引;如果是ALL,说明没走索引 -
rows预估是否接近实际数据量 —— 过高说明统计信息不准,可能需要ANALYZE TABLE - 是否出现
Using temporary或Using filesort—— 这些是性能红灯
真正麻烦的不是语法选哪个,而是外键缺失、索引没建、统计信息陈旧——这些会让任何写法都变慢。











