
子查询能直接在SQL里复用计算结果,但写错位置或忽略返回值约束,轻则查不出数据,重则整个查询卡死。
WHERE里用IN还是EXISTS?看你要判断“属于集合”还是“存在关联”
想查“所有下过 pending 订单的用户”,用IN看似直觉: WHERE user_id IN (SELECT user_id FROM orders WHERE status = 'pending')。但只要子查询里user_id出现NULL(比如某条订单没填用户ID),整条IN表达式就变成UNKNOWN,这一行直接被过滤掉——不报错,也不提示。
- 改用
EXISTS更稳:WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.status = 'pending'),它只关心是否存在匹配行,遇到NULL也不影响逻辑 -
EXISTS通常比IN快,尤其子查询结果集大时,它找到第一行就停;而IN得把全部结果拉出来再做哈希或遍历 - 如果非用
IN,至少加AND user_id IS NOT NULL过滤掉空值
SELECT列表里的标量子查询必须单行单列,否则直接报错
写SELECT name, (SELECT AVG(salary) FROM employees e2 WHERE e2.dept_id = e1.dept_id) AS dept_avg这类语句时,数据库要求括号里必须返回且仅返回一个值。一旦漏写WHERE、或GROUP BY没对齐,就会触发ERROR 1242: Subquery returns more than 1 row。
- 确保子查询条件能唯一确定一行,比如部门ID必须和外层表字段严格关联
- 聚合函数如
AVG()、MAX()本身返回单值,但得确认WHERE条件没失效(例如dept_id为空时,AVG()仍会返回NULL,不算错误,但业务上可能要处理) - 关联字段(如
e2.dept_id = e1.dept_id)必须有索引,否则10万行主表=执行10万次全表扫描
FROM里的子查询当派生表,别名不是可选项,是语法强制要求
想先按班级算平均分,再筛出平均分>90的班级,写成SELECT class, avg_score FROM (SELECT class, AVG(score) AS avg_score FROM scores GROUP BY class)会直接报错:ERROR 1248: Every derived table must have its own alias。
- 必须加别名,写成
... FROM (SELECT ...) AS temp或... FROM (SELECT ...) temp - 外部查询中所有列都得带别名引用,比如
temp.avg_score,不能直接写avg_score - 别名作用不只是语法合规,更是避免列名冲突——比如子查询里叫
id,外层表也有id,不加前缀根本分不清
关联子查询性能差,不是不能用,而是得先看EXPLAIN再动手
像SELECT u.name, (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) FROM users u这种写法,表面简洁,实际是N+1查询:users表每读一行,就触发一次orders表的全量扫描。
- 先跑
EXPLAIN看执行计划,重点关注type是不是ALL(全表扫描)、rows是不是爆炸增长 - 优先加索引:
CREATE INDEX idx_orders_user_id ON orders(user_id) - 能用
JOIN替代就替代,尤其是要聚合统计时:SELECT u.name, COUNT(o.id) FROM users u LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id - MySQL 8.0.14+支持
LATERAL,可控制子查询执行时机,但得确认版本,且语法更绕,不如先优化索引和JOIN
子查询最危险的不是写不出来,而是写出来能跑、结果也看似合理,但一到生产环境就拖慢整个库——问题往往藏在NULL处理、索引缺失或隐式全表扫描里。











