子查询在mysql 5.7及更早版本中易被标记为dependent subquery,导致主表每行重复执行;关联查询(如left join)性能更优,但改写需确保语义等价、null处理一致、避免行数膨胀,并检查索引、null逻辑及条件位置。

因为子查询在 MySQL 5.7 及更早版本中常被标记为 DEPENDENT SUBQUERY,导致主表每行都触发一次子查询执行;而关联查询(如 LEFT JOIN)可走索引嵌套循环或哈希连接,且支持条件下推和提前过滤。
看到 EXPLAIN 里有 DEPENDENT SUBQUERY 就该警觉
这表示子查询引用了外层表字段(比如 u.id = o.user_id),优化器无法物化结果,只能对主表每一行重复执行。10 万行订单?就是 10 万次子查询——每次都要解析、权限检查、临时表创建、再销毁。
- 用
EXPLAIN FORMAT=TREE能更清楚看到嵌套层级,若节点含dependent subquery且Extra出现Using temporary; Using filesort,基本确认未物化 -
IN和=子查询更容易触发依赖执行;EXISTS虽语义不同,但同样逃不开相关性判断 - 哪怕子查询只返回单行,只要带外层列引用,就难逃重复执行命运
不是所有子查询都能直接换成 JOIN
机械替换会出错。关键看语义是否等价、NULL 处理逻辑是否一致、会不会放大行数。
- 可安全改写:
SELECT o.id, (SELECT u.name FROM users u WHERE u.id = o.user_id)→LEFT JOIN users u ON u.id = o.user_id - 不能直接改写:
SELECT * FROM orders WHERE id IN (SELECT order_id FROM order_items GROUP BY order_id HAVING COUNT(*) > 3)—— 含聚合,JOIN 后必须加GROUP BY和HAVING,否则逻辑错 - 一对多关系(如订单→订单项)直接
JOIN会膨胀主表行数,要用DISTINCT或改用EXISTS
改写前必须检查的三件事
漏掉任一细节,性能可能更差,甚至结果错误。
- 连接字段是否有索引:
orders.user_id和users.id都得有单独索引或联合索引,否则JOIN反而变ALL扫描 - NULL 怎么处理:
NOT IN (subquery)对 NULL 不安全,改LEFT JOIN ... IS NULL时,若子表连接字段允许 NULL,得额外加AND right.id IS NOT NULL - WHERE 条件别错放:子查询带过滤(如
u.status = 'active')要写进ON,写进WHERE会把LEFT JOIN退化成INNER JOIN
最易被忽略的是:子查询里一个 ORDER BY RAND() 或 GROUP BY 就会让物化失效,而你可能还信誓旦旦以为它被缓存了。











