EXISTS比LEFT JOIN快仅限存在性判断:外表小、内表大且有索引时,EXISTS可提前终止;但手动子查询未必提速,真正起效的是优化器自动重写的半连接。

EXISTS 比 LEFT JOIN 更快,但仅限于存在性判断;多数所谓“子查询替代 JOIN 提速”的说法,实际是误读或场景错配——真正能提效的,几乎都是优化器自动重写的结果,不是你手动写的子查询。
什么时候 EXISTS 确实比 JOIN 快
- 外表小、内表大,且只需确认“是否存在匹配行”,不关心匹配内容
- 内表关联字段有索引,
EXISTS可提前终止(找到第一行即返回TRUE) - 不需要聚合、去重或返回右表字段,纯条件过滤
SELECT u.id, u.name FROM users u WHERE EXISTS ( SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.status = 'paid' );
- 这种写法在 MySQL 5.6+ 和 PostgreSQL 中大概率被优化器转为半连接(semi-join),执行计划里看不到
Subquery节点,而是Hash Semi Join或Index Lookup - 若手动改成
LEFT JOIN ... WHERE o.id IS NOT NULL,反而多一次连接和空值判断,还可能因GROUP BY或DISTINCT引入临时表
为什么 IN 子查询常被误认为“比 JOIN 快”
- 实际上,MySQL 5.6+ 默认启用
semijoin优化,WHERE col IN (SELECT ...)会被自动压平成连接操作 - 但以下情况会退化:
- 子查询含
ORDER BY/LIMIT/HAVING,优化器无法下推,被迫物化为无索引临时表 - 子查询结果集过大(如 > 数千行),哈希表溢出到磁盘,IO 暴增
- 外表字段与子查询字段字符集不一致(如
utf8mb4vsutf8),导致隐式转换,索引失效
- 子查询含
NOT EXISTS 和 LEFT JOIN ... IS NULL 的性能差异
- 语义等价,但执行路径不同:
-
NOT EXISTS:优化器倾向用反向索引查找或Anti Join(PostgreSQL/SQL Server 支持,MySQL 8.0.24+ 开始实验性支持) -
LEFT JOIN ... WHERE right.id IS NULL:必须完成全部连接,再过滤空值,中间结果集可能极大
-
- 实测中,当右表有高效索引(如
(user_id, status))时,NOT EXISTS的rows扫描数通常比LEFT JOIN少 30%–70%
容易被忽略的关键点
- 手动写子查询不会“天然更快”,真正起作用的是优化器是否识别出可重写模式
-
EXPLAIN中看到select_type = DEPENDENT SUBQUERY就说明它正在逐行执行——这是性能杀手,不是优化方案 - 如果业务逻辑本质是“取最新一条记录”(如最新物流、最新评论),
LATERAL或窗口函数 +ROW_NUMBER()是唯一可控解法,IN/EXISTS都无法正确表达
别迷信“子查询替代 JOIN”,先看执行计划里到底扫了几行、有没有临时表、用没用上索引。否则改来改去,只是把慢 SQL 从一种写法换成另一种写法。


















