mysql 5.7子查询慢的根本原因是优化器未物化相关子查询,导致外层每行触发一次内层执行;explain中出现dependent subquery即表明该问题,必须通过索引优化、改写为join/exists或添加物化提示来解决。

MySQL 5.7 中子查询慢,根本不是语法问题,而是优化器对相关子查询(DEPENDENT SUBQUERY)基本不物化、不复用结果——外层每扫一行,内层就重跑一遍,10 万行就是 10 万次扫描。
看到 DEPENDENT SUBQUERY 就该动手改写
这是 EXPLAIN 里最危险的信号:它明确告诉你,外层每一行都在触发一次完整子查询执行。比如 SELECT * FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id),若 users 有 50 万行,且 orders(user_id) 没索引,实际就是 50 万次全表扫描。
-
EXPLAIN中select_type列显示DEPENDENT SUBQUERY,或Extra出现Using where但rows极小、loops极大(如loops=48231),基本坐实该模式 - 哪怕子查询带
LIMIT 1,5.7 也大概率不识别为半连接(semi-join),必须显式改写或加/*+ SEMIJOIN() */提示 - 子查询返回多列或多行时,中间结果无法缓存,反复计算;而 JOIN 可走
Index Nested-Loop或哈希路径,支持提前过滤
JOIN 不自动快,索引和驱动顺序才是关键
写成 JOIN 不等于性能提升——没索引,它照样退化成嵌套循环,甚至比子查询更糟(还要处理重复行、临时表落盘)。
-
ON字段必须都有索引:orders.customer_id和customers.id都得单独建索引,或组合成联合索引 - 小表没被选为驱动表?MySQL 可用
STRAIGHT_JOIN强制,但得先用EXPLAIN确认当前计划是否真卡在驱动顺序上 -
LEFT JOIN大表却没加WHERE过滤,中间结果集可能暴涨数倍,内存撑不住就会落盘临时表——此时比原子查询还慢
不是所有子查询都该被替换
标量子查询(返回单值)在有索引时反而更轻量,强行 JOIN 可能画蛇添足。
- 例如
SELECT id, name, (SELECT MAX(created_at) FROM logs l WHERE l.user_id = u.id) FROM users u,只要logs(user_id, created_at)有索引,每次只做一次索引查找,开销极低 - 等价的
LEFT JOIN logs GROUP BY u.id会先生成所有匹配行再聚合,中间结果集可能远大于用户数 - 但同一查询里多个标量子查询(查
MAX、COUNT、AVG各一次),它们各自执行——这时提前物化或改用窗口函数更稳妥
真正难的不是把 IN 换成 JOIN,而是判断这个子查询是否依赖外层值、是否含聚合、是否允许 NULL、主子表是否一对多——漏掉任一条件,结果就不可信。











