子查询在 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.uidMySQL 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字段。
