子查询在执行计划中显示为DERIVED或SUBQUERY时,应优先改写为JOIN、确保子查询内部走索引、避免SELECT*、为子查询条件字段建索引,并检查物化是否合理。
子查询在执行计划里显示为 DERIVED 或 SUBQUERY 时怎么处理
navicat 中点「解释」看到 select_type 是 derived 或 subquery,基本说明 mysql 把子查询单独执行、结果存临时表再参与外层计算——这非常容易拖慢性能,尤其当子查询返回大量行或没走索引时。
常见表现:执行计划里 rows 显著偏高、Extra 出现 Using temporary 或 Using filesort、整体 cost 占比突兀。
- 先确认子查询是否真有必要:能用
JOIN替代的,优先改写。例如WHERE id IN (SELECT user_id FROM logs)改成INNER JOIN logs ON t.id = logs.user_id - 检查子查询内部是否有过滤条件缺失:比如漏写了
WHERE或用了函数导致索引失效(DATE(created_at)就会让created_at索引失效) - 若子查询必须存在,给其
WHERE条件字段加索引——注意是子查询自己的表,不是外层表 - 避免在子查询中用
SELECT *:只选需要字段,减少临时表体积和网络传输量
Navicat 里怎么看子查询有没有走索引
关键看执行计划中子查询对应那行的 type 和 key 字段。如果 type 是 ALL 或 index,且 key 为 NULL,基本就是全表扫描了。
特别注意:子查询的执行计划是嵌套显示的,id 值更大的那一组才是子查询本身(比如外层 id=1,子查询可能是 id=2 或更高)。
- 用
EXPLAIN FORMAT=TRADITIONAL或EXPLAIN FORMAT=JSON查看更清晰的嵌套结构 -
key_len值偏小(比如只用了索引前缀)说明索引没充分利用,可能需要调整索引顺序或补充覆盖字段 - 如果子查询用了
GROUP BY或ORDER BY,但没命中索引,Extra会标出Using temporary; Using filesort,这是典型优化点
MySQL 8.0+ 的物化子查询(Materialize)要不要干预
MySQL 8.0 引入了子查询物化优化,默认把某些子查询结果缓存到内部临时表。Navicat 执行计划里如果看到 select_type=SUBQUERY 但 rows 很低、Extra 有 Starts 字段,大概率是物化生效了——这时不一定需要改写,反而要警惕过度物化带来的内存压力。
- 观察
Starts值:如果远大于 1(比如 1000),说明该子查询被反复执行,应优先考虑改写为JOIN或添加关联条件减少重复调用 - 用
optimizer_switch='materialization=off'临时关闭物化测试性能,但不建议长期关闭——它多数时候是有益的 - 物化依赖统计信息准确度,如果发现物化后变慢,先运行
ANALYZE TABLE更新子查询涉及表的统计信息
Navicat 自带的「SQL 调优器」对子查询有用吗
Navicat Premium 内置的 SQL 调优器(需开启)能识别部分子查询问题,比如提示「子查询未使用索引」或「建议改用 JOIN」,但它不会自动重写 SQL,也不能处理复杂嵌套逻辑。
真正有效的做法还是人工结合执行计划判断:
- 调优器提示「子查询返回行数过多」,就去查那个子查询单独执行的
EXPLAIN,而不是只看外层 - 它有时会忽略
IN子查询中的NULL安全问题(如WHERE id IN (SELECT x FROM t WHERE y IS NOT NULL)可能因 NULL 导致意外逻辑),这类必须手动验证 - 对含聚合的子查询(
SELECT MAX(time) FROM log WHERE user_id = t.id),调优器通常无建议,得靠你加(user_id, time)联合索引
子查询优化最易被忽略的点是:人总盯着外层 WHERE,却忘了子查询自己也是独立查询,它的执行路径、索引、统计信息都得单独看。Navicat 的执行计划窗口里,每一行都值得点开细读,别只扫一眼 type 就跳过。











