Dependent Subquery 慢是因为外层每行都重复执行子查询,如10万行则执行10万次;JOIN改写需满足三前提:位置在WHERE/SELECT、单列依赖、无聚合;改写后须验证去重和NULL逻辑。

Dependent Subquery 为什么慢
MySQL 执行 Dependent Subquery(相关子查询)时,会为外层查询的**每一行**都重新执行一次子查询。比如 WHERE EXISTS (SELECT 1 FROM logs WHERE logs.order_id = orders.id),若 orders 返回 10 万行,子查询就可能被执行 10 万次——哪怕 logs.order_id 有索引,也逃不开重复解析、重复走索引查找的开销。
用 EXPLAIN 查看执行计划时,如果 select_type 列显示为 DEPENDENT SUBQUERY,基本可以判定这是性能瓶颈点。
JOIN 重写必须满足的三个前提
不是所有嵌套查询都能无损转成 JOIN,以下条件缺一不可:
- 子查询出现在
WHERE或SELECT中,且逻辑是「基于外层某字段做存在性/值匹配」,例如IN、EXISTS、= (SELECT ...) - 子查询不依赖外层多列(如同时用
orders.id和orders.status做条件),否则 JOIN 条件会变复杂,易出错 - 子查询结果集不包含聚合或
GROUP BY—— 否则需先用派生表封装,再 JOIN
常见场景的 JOIN 改写对照
以下改写保持语义一致,且能显著降低执行耗时(实测大表下 QPS 提升 3–10 倍):
场景 1:用 IN 查找关联记录
原写法:SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE status = 'paid');
改写为:SELECT DISTINCT u.* FROM users u INNER JOIN orders o ON u.id = o.user_id WHERE o.status = 'paid';
场景 2:用 NOT IN 排除无关联记录
原写法(还隐含 NULL 风险):SELECT * FROM customers WHERE id NOT IN (SELECT customer_id FROM payments WHERE amount > 0);
改写为(更安全、更快):SELECT c.* FROM customers c LEFT JOIN payments p ON c.id = p.customer_id AND p.amount > 0 WHERE p.customer_id IS NULL;
场景 3:标量子查询转 JOIN + 聚合
原写法:SELECT id, (SELECT MAX(created_at) FROM events WHERE user_id = users.id) AS last_event FROM users;
改写为:SELECT u.id, e.max_created AS last_event FROM users u LEFT JOIN (SELECT user_id, MAX(created_at) AS max_created FROM events GROUP BY user_id) e ON u.id = e.user_id;
改写后必须验证的两件事
JOIN 不是银弹,改完立刻跑不通或结果错位很常见:
- 检查是否引入了重复行:INNER JOIN 可能因一对多关系导致主表记录膨胀,必要时加
DISTINCT或用GROUP BY去重 - 确认 NULL 处理逻辑:原
NOT IN遇到子查询含 NULL 会整个返回空,而LEFT JOIN ... IS NULL不受此影响——这反而是优势,但得明确业务是否依赖旧行为
最稳妥的做法是:在测试库导出小批量数据,用 SELECT ... INTO OUTFILE 分别跑原查询和新查询,再用 diff 对比结果文件。别只看前 10 行。


















