DEPENDENT SUBQUERY几乎总是性能瓶颈,必须替换;其导致外层每行触发内层全扫描,即使有索引也难以承受;应优先改用LEFT JOIN+GROUP BY、LATERAL或APPLY,标量子查询在单值+复合索引时更轻量。

没有绝对答案——JOIN不一定比子查询快,但DEPENDENT SUBQUERY几乎总是慢的源头。
看到 EXPLAIN 里有 DEPENDENT SUBQUERY 就该立刻动手
这不是“要不要换”的问题,而是“必须换”的信号。它代表外层每行都触发一次内层全扫描,10 万行用户就扫 10 万次订单表,哪怕加了索引也扛不住。
-
EXPLAIN输出中select_type列显示DEPENDENT SUBQUERY,或Extra出现Using where; Using index但rows极小、loops极大(比如loops=48231),基本就是它 - 常见写法:
WHERE id = (SELECT user_id FROM logs WHERE user_id = users.id ORDER BY ts DESC LIMIT 1)、EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.status = 'paid') - 改写优先级:用
LEFT JOIN+GROUP BY或LATERAL(PostgreSQL)、APPLY(SQL Server);MySQL 可考虑STRAIGHT_JOIN控制驱动表顺序
标量子查询有时反而更轻量
当子查询只返回单值、且能走索引快速定位时,它比 JOIN + GROUP BY 更省资源——不生成中间膨胀行。
- 例如:
SELECT id, name, (SELECT MAX(created_at) FROM logs l WHERE l.user_id = u.id),只要logs(user_id, created_at)有复合索引,每次就是一次索引MAX查找 - 等价的
LEFT JOIN logs GROUP BY u.id会先拉出所有匹配日志行,再聚合,中间结果集可能比用户数大几十倍 - 但注意:多个标量子查询(如同时查
MAX、COUNT、AVG)可能各自执行——这时窗口函数更稳:COUNT(*) OVER (PARTITION BY u.id)
IN 子查询在 MySQL 5.6+ 多数会被转成 semi-join,但有条件
别急着重写,先看 EXPLAIN —— 它可能已经优化过了。
- 能自动转换的前提:子查询里不能有
GROUP BY、ORDER BY、LIMIT、WINDOW函数,也不能是相关子查询 - 如果
EXPLAIN显示type: eq_ref或出现Using join buffer,说明已走 semi-join;若看到Using temporary,大概率被物化到磁盘临时表,得优化 -
NOT IN基本不会被优化,仍走DEPENDENT SUBQUERY,优先换成NOT EXISTS
索引失效比写法选择更致命
无论你写 JOIN 还是子查询,只要连接字段或 WHERE 条件没走索引,性能差距毫无意义。
-
WHERE status IN ('active', 'pending')如果status列没索引,IN子查询和JOIN都得全表扫描 - 复合索引顺序决定一切:
SELECT * FROM orders WHERE user_id = ? AND created_at > ?必须建(user_id, created_at)索引,反过来就没用 -
LEFT JOIN大表却没加WHERE过滤,中间结果集可能暴涨数倍,内存撑不住就会落盘临时表——此时比子查询还慢
真正要盯的是执行计划里的 type 和 rows:如果 type 是 ALL 或 index,且 rows 远大于最终结果行数,基本就是相关子查询在反复扫表,或者索引根本没被用上。


















