子查询在 MySQL 中不是“可选技巧”,而是解决多表逻辑依赖的刚需手段。只要需要基于某次查询结果再做筛选、计算或关联,就绕不开它。
子查询必须用括号包裹,且不能加 LIMIT
MySQL 要求所有子查询必须用
()明确包裹,否则直接报错
ERROR 1064。另外,子查询内部不能直接使用
LIMIT(除非配合
ORDER BY且位于派生表位置)。 ✅ 正确:
SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE amount > 100)❌ 错误:
SELECT * FROM users WHERE id IN SELECT user_id FROM orders LIMIT 10(缺括号 + 多余 LIMIT) ⚠️ 特殊可用:
SELECT * FROM (SELECT * FROM logs ORDER BY ts DESC LIMIT 5) AS recent(仅限 FROM 子句中的派生表)
WHERE 中的标量子查询必须返回单值
当子查询出现在
WHERE条件右侧(如
=、
>、
BETWEEN等)时,它必须只返回一行一列,即“标量”。否则会触发
ERROR 1242: Subquery returns more than 1 row。 用
MAX()/
MIN()/
AVG()聚合确保单值:
WHERE salary > (SELECT AVG(salary) FROM employees)用
LIMIT 1强制截断(慎用,语义可能不严谨):
WHERE dept_id = (SELECT id FROM departments WHERE name = 'HR' LIMIT 1)避免写成:
WHERE status = (SELECT status FROM audits WHERE ref_id = 123)(若有多条 audit 记录就崩)
IN 和 EXISTS 的语义与性能差异很实际
IN检查值是否在结果集中,适合子查询结果集小、主表大;
EXISTS是半连接语义,只关心是否存在匹配行,适合子查询结果集大、或需关联外层字段时。
SELECT name FROM customers c WHERE EXISTS ( SELECT 1 FROM orders o WHERE o.customer_id = c.id AND o.status = 'shipped' );
IN子查询无法引用外部表字段:
... WHERE id IN (SELECT customer_id FROM orders WHERE status = 'shipped')(只能查全部)
EXISTS子查询可关联外部字段(如上例中的
c.id),这是它不可替代的关键点 若子查询结果为空,
IN返回空集,
EXISTS返回
FALSE—— NULL 处理逻辑不同,会影响
NOT IN的行为(
NOT IN遇到 NULL 直接整个条件为 UNKNOWN)
子查询最易被忽略的其实是执行顺序:MySQL 通常先执行外层查询的驱动表,再对每一行执行子查询(相关子查询),这意味着 N×M 次扫描风险真实存在。别只盯着语法对不对,先看执行计划里的
DEPENDENT SUBQUERY出现了几次。
