mysql中子查询与联接查询的优化比较

来源:这里教程网 时间:2026-02-28 20:40:40 作者:

子查询在 WHERE 中被 MySQL 重写为关联时,性能未必差

MySQL 5.6+ 对很多

IN
EXISTS
子查询做了自动半连接(semi-join)优化,会把形如
SELECT * FROM t1 WHERE id IN (SELECT id FROM t2)
重写为等价的
JOIN
执行。是否触发该优化,取决于子查询是否满足“可物化”“无相关列”等条件。

实操建议:

EXPLAIN
查看执行计划,重点看
select_type
字段:若显示
DEPENDENT SUBQUERY
,说明未优化;若为
SIMPLE
或出现
FirstMatch
/
LooseScan
,说明已转为半连接
避免在子查询中使用
ORDER BY
LIMIT
或外部表字段(即相关子查询),否则大概率无法重写
对小结果集子查询(如
SELECT status FROM config WHERE key = 'mode'
),直接内联比
JOIN
更轻量,MySQL 通常会自动物化为常量

LEFT JOIN 后加 WHERE 条件可能意外转成 INNER JOIN

这是最常踩的坑:写

LEFT JOIN t2 ON t1.id = t2.t1_id WHERE t2.status = 'active'
,逻辑上想保留所有
t1
行、只过滤匹配的
t2
,但
WHERE
会把
t2.status
NULL
的行全部剔除——实际效果等同于
INNER JOIN

正确做法是把过滤条件移到

ON
子句:

SELECT t1.*, t2.name
FROM t1
LEFT JOIN t2 ON t1.id = t2.t1_id AND t2.status = 'active';

注意:

ON
中的条件只影响连接行为,
WHERE
中的条件作用于最终结果集。若需保留
t1
全量且只取特定
t2
,必须用
ON
过滤;若真要排除
t1
中无匹配
t2
的记录,才用
WHERE

子查询在 SELECT 列表中(标量子查询)极易引发性能雪崩

形如

SELECT id, (SELECT COUNT(*) FROM log WHERE log.user_id = user.id) AS cnt FROM user
是典型陷阱:MySQL 会为每一行
user
执行一次子查询,复杂度 O(N×M),且无法利用
user.id
上的索引加速子查询内部扫描。

优化方向明确:

改写为
LEFT JOIN ... GROUP BY
,让聚合在连接后一次性完成
确保子查询中的关联字段(如
log.user_id
)有索引,否则每次子查询都全表扫
log
若子查询结果稳定(如统计月活),考虑用物化视图或缓存表替代实时计算

EXISTS 比 IN 更适合检查存在性,尤其当子查询结果含 NULL

IN
遇到子查询返回
NULL
时,整个表达式结果为
UNKNOWN
,导致行被过滤(即使其他条件为真);而
EXISTS
只关心是否存在匹配行,不关心值是否为
NULL
,语义更清晰、行为更可控。

示例对比:

SELECT * FROM t1 WHERE t1.id IN (SELECT t2.id FROM t2); -- 若 t2.id 有 NULL,整行失效
SELECT * FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.id = t1.id); -- 安全

另外,

EXISTS
在找到第一行匹配后即停止,而
IN
子查询可能需生成完整结果集再做哈希查找——对大数据集,
EXISTS
常有更低延迟。

真正难处理的是多层嵌套相关子查询,它既难读又难优化,一旦出现,优先重构为

JOIN
+
GROUP BY
或临时表分步计算。

相关推荐