mysql 5.7子查询性能差主因是优化器未正确物化或误判为相关子查询;若explain中select_type为dependent subquery且extra反复执行,即属嵌套循环,性能急剧下降。

因为 MySQL 5.7 默认不物化子查询,也不下推条件,外层每行都重跑一遍内层逻辑——不是“慢”,是反复执行。
DEPENDENT SUBQUERY 是性能雷区的明确信号
只要 EXPLAIN 中出现 select_type = DEPENDENT SUBQUERY,就说明优化器把它当成了“相关子查询”:外层查出一行,就完整执行一次子查询。10 万行主表?就是 10 万次解析、计划、扫描、过滤。
- 典型表现:
Extra列反复出现Using where,rows值不是单个估算,而是随外层行数波动 - 哪怕子查询本身只查几千行,每次都要走全表或索引扫描,I/O 和 CPU 开销线性放大
-
NOT IN更危险:会额外触发临时表物化,且遇到NULL直接返回空结果,逻辑也难调试
MySQL 5.7 的物化机制有硬限制
5.7 支持 MATERIALIZED,但只在满足特定条件时才启用——它不是“自动聪明”,而是“有条件妥协”。
- 子查询不能含外部引用(比如
WHERE t2.id = t1.ref_id),否则直接退化为嵌套循环 - 统计信息过期(
ANALYZE TABLE没跑过)会让优化器低估子查询行数,强行物化后内存撑爆,降级成磁盘临时表 - 加了
/*+ MATERIALIZE */提示也不一定生效:5.7 对 optimizer hints 支持有限,且只对部分引擎有效 - 如果子查询里用了函数(如
DATE(created_at))、隐式类型转换(INTvsBIGINT UNSIGNED),物化直接被跳过
分步骤执行为什么反而稳
人工拆开,等于绕过优化器的误判,把控制权拿回来。
- 先
CREATE TEMPORARY TABLE tmp AS SELECT DISTINCT user_id FROM logs WHERE status = 'error',显式建带索引的临时表 - 再
SELECT * FROM users WHERE id IN (SELECT id FROM tmp),这时tmp是真实表,能走索引、能统计、能预估 - 比依赖优化器自动物化更可靠,尤其适合多层嵌套、含
GROUP BY或窗口函数的场景 - 代价是多一次写入和清理,但换来的是可预测的执行时间和稳定的
rows量级
真正卡住的往往不是语法,而是优化器在“该不该物化”“走 JOIN 还是子查询”之间做了错误成本估算——它没看到你数据的真实分布,你得亲手告诉它怎么走。











