dependent subquery 是慢查询主因,会导致外层每行触发一次内层全扫描;应优先改写为 join 或 lateral/apply,但需确保索引、驱动表顺序正确;标量子查询在有索引时可能更高效;in 子查询易触发物化陷阱,需谨慎优化。

JOIN 不一定比子查询快,但绝大多数慢查询的根因是写出了 DEPENDENT SUBQUERY —— 它会让数据库对外层每一行都重跑一次内层查询,10 万行就是 10 万次扫描。
看到 EXPLAIN 里出现 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,或Extra出现Using where; Using index但rows极小、loops极大(如loops=48231),基本可以确认是这个模式 - 这种场景下,改用
JOIN或LATERAL(PostgreSQL)、APPLY(SQL Server)几乎总能消除重复执行
JOIN 的连接算法由优化器选,但前提是你给了它选择权
写 JOIN 不等于自动获得哈希连接。真正起作用的是索引、统计信息和驱动表顺序。
- 如果
ON字段没索引,优化器大概率退化为嵌套循环(Nested Loop),性能和相关子查询实际相当,甚至更差(因为还要处理中间结果膨胀) - 小表没被选为驱动表?MySQL 可加
STRAIGHT_JOIN强制,PostgreSQL 可用/*+ leading(t1) */提示,但必须先用EXPLAIN (ANALYZE)确认当前计划 -
LEFT JOIN大表却没加WHERE过滤,中间结果集可能暴涨数倍,内存撑不住就会落盘临时表 —— 此时比子查询还慢
不是所有子查询都该被替换,标量子查询有时更轻量
当子查询返回单值、且能走索引快速定位时,它反而比 JOIN + GROUP BY 更省资源。
- 例如
SELECT id, name, (SELECT MAX(created_at) FROM logs l WHERE l.user_id = u.id) FROM users u,只要logs(user_id, created_at)有索引,这个子查询每次只做一次索引 MIN/MAX 查找,开销极低 - 而等价的
LEFT JOIN logs GROUP BY u.id会先生成所有匹配行,再聚合,中间结果集可能远大于用户数 - 但注意:如果同一个查询里写了多个标量子查询(比如同时查
MAX、COUNT、AVG),它们可能各自执行 —— 这时提前物化或改用窗口函数更稳妥
IN 子查询在 MySQL 里容易触发物化陷阱
MySQL 对 IN (SELECT ...) 的处理很敏感,尤其当子查询含 LIMIT、ORDER BY 或聚合但没 GROUP BY 时,常强制落盘临时表。
-
EXPLAIN中出现Using temporary; Using filesort是明确警告 - 把
WHERE id IN (SELECT id FROM t WHERE ... LIMIT 1000)改成JOIN (SELECT id FROM t WHERE ... ORDER BY x LIMIT 1000) AS tmp ON a.id = tmp.id,能绕过物化,让优化器直接走索引 - 注意:
IN子查询结果为空时返回空集,而JOIN会丢数据,需根据业务判断用LEFT JOIN还是保留语义
真正卡住性能的从来不是语法本身,而是执行路径是否重复、中间结果是否失控、索引是否落在关键字段上。别信“用 JOIN 就快”,先看 EXPLAIN 里的 loops 和 rows,再决定动哪一行 SQL。











