WHERE子句中的子查询是条件表达式的一部分,必须返回单值(=、>等)或单列多行(IN、EXISTS等),且需避免NULL导致逻辑失效;FROM子句子查询须加别名并作为临时表使用;SELECT子句子查询每行执行一次,易引发N+1性能问题。

WHERE子句里的子查询是用来过滤行的
它本质是“条件表达式的一部分”,返回值必须能和左侧字段做逻辑比较。比如 salary > (SELECT AVG(salary) FROM emp),括号里必须产出一个单值;如果写成 id IN (SELECT id FROM orders),那子查询就得返回一列多行——但不能返回两列或空结果集(除非用 EXISTS)。
常见错误现象:Subquery returns more than 1 row 就是因为用了 = 却返回了多行;或者 IN 子查询里混进了 NULL,导致整行被静默丢弃(三值逻辑问题)。
- 单行操作符(
=、>、)只接受标量子查询 - 多行操作符(
IN、ANY、ALL)要求子查询返回单列、可为空或多行 - 用
EXISTS替代IN能避开NULL坑,且多数引擎会提前终止扫描
FROM子句里的子查询是当作一张临时表用的
它不参与条件判断,而是提供数据源。语法上必须加别名,否则 MySQL 直接报错:Every derived table must have its own alias。例如 (SELECT user_id, COUNT(*) AS cnt FROM log GROUP BY user_id) AS t,这个 t 就是主查询能 JOIN 或 SELECT 的合法表名。
性能影响很实在:MySQL 5.7+ 可能内联优化,但一旦子查询含聚合、排序或 LIMIT,就大概率被物化成临时表,无法下推外部 WHERE 条件——意味着可能全量算完再过滤,大表时特别慢。
- 子查询返回结构必须明确(列名、类型),否则外部引用会失败
- 别名不是可选的,是强制语法要求
- 嵌套太深(比如
FROM (SELECT * FROM (SELECT ...) AS t1) AS t2)会让执行计划变复杂,优先考虑 CTE 拆解
SELECT子句里的子查询最容易拖慢查询
它每行执行一次,属于典型的“N+1”场景。比如 SELECT id, (SELECT COUNT(*) FROM order WHERE user_id = u.id) FROM user u,如果 user.id 没索引,或子查询没走 order.user_id 索引,就会对每个用户扫一遍订单表。
这不是语法错误,而是隐性性能陷阱。你看到结果正确,但执行时间可能从毫秒级跳到秒级甚至超时。
- 务必确认子查询中的关联字段有对应索引
- 当标量子查询逻辑复杂时,改用 LEFT JOIN + GROUP BY 通常更快
- MySQL 8.0+ 支持 LATERAL,可在 FROM 中关联驱动,但兼容性仍需验证
关键点不在“能不能写”,而在“执行时数据库怎么理解它”。WHERE 子句的子查询是条件求值器,FROM 子句的是数据提供者,SELECT 子句的是逐行计算器——角色不同,约束和代价就完全不同。别只看语法像不像,得盯住执行计划里的 Extra 字段,尤其是 “Using temporary” 和 “Using filesort” 这类信号。

















