子查询在MySQL中并非总是先执行,而是由优化器根据类型、相关性等决定执行策略;非相关标量子查询通常提前计算,关联子查询可能逐行执行或被重写为半连接。

子查询在 MySQL 中是先执行还是后执行?
MySQL 执行含子查询的 SQL 时,并不总是“先内后外”。它会根据子查询类型、是否相关、优化器判断,决定是物化(Materialize)、转为连接(JOIN),还是延迟执行。简单 SELECT 中的非相关标量子查询(如 (SELECT COUNT(*) FROM t2))通常会被提前计算一次;但出现在 WHERE 中的关联子查询(如 WHERE id IN (SELECT user_id FROM orders WHERE orders.user_id = users.id))可能对每行外层数据都重新执行——除非被优化器重写为半连接(semi-join)。
如何判断子查询是否被优化器重写?
用 EXPLAIN 查看执行计划是最直接的方式。重点关注以下几点:
-
type列出现ALL+Extra含Using where; Using join buffer,大概率是未优化的嵌套循环式执行 -
Extra出现Start temporary或End temporary,说明子查询被物化成临时表 -
select_type显示DEPENDENT SUBQUERY表示关联子查询,SUBQUERY表示非关联 - 若
Extra含FirstMatch(users)或LooseScan,说明已转为半连接优化
哪些写法会让子查询性能急剧下降?
这些常见写法极易触发低效执行路径:
- 在
WHERE中使用NOT IN (subquery)且子查询结果含NULL:导致整个条件恒为UNKNOWN,常被误判为全表扫描 - 用
SELECT * FROM t1 WHERE id = (SELECT id FROM t2 WHERE ...)但子查询可能返回多行:运行时报错Subquery returns more than 1 row - 在
SELECT列表中写关联标量子查询,如(SELECT name FROM dept WHERE dept.id = emp.dept_id):MySQL 8.0 前无法物化,每行都查一次 - 子查询里含
ORDER BY + LIMIT却没加GROUP BY或确定性排序依据:结果不可靠,且优化器难做等价改写
替代子查询的更稳写法有哪些?
多数场景下,JOIN 或窗口函数比子查询更可控、更易优化:
- 把
WHERE x IN (SELECT y FROM t2)改成INNER JOIN t2 ON t1.x = t2.y(去重需加DISTINCT或用EXISTS) - 把
SELECT (SELECT MAX(time) FROM log l WHERE l.uid = u.id)改成LEFT JOIN (SELECT uid, MAX(time) AS last_time FROM log GROUP BY uid) l ON u.id = l.uid - MySQL 8.0+ 中,用
ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)替代“取每个分组最新一条”的相关子查询 - 避免在
UPDATE/DELETE中直接引用同一张表的子查询,应套一层SELECT包装成派生表,否则报错You can't specify target table for update in FROM clause
子查询逻辑清晰,但执行细节藏在优化器决策背后。真正影响性能的往往不是“有没有子查询”,而是“有没有索引支撑子查询的驱动条件”以及“优化器能否识别出等价 JOIN 形式”。别只盯着语法结构,多看 EXPLAIN 输出里的 rows 和 Extra 字段。










