应优先分析子查询的执行耗时而非行数:postgresql看subquery scan的actual total time,mysql用explain format=json查subquery/derived的rows与filtered,若rows大且filtered低则索引失效。

怎么看 EXPLAIN 里哪个子查询最拖后腿
嵌套查询慢,不是“整个查询慢”,而是某个子查询在执行计划里占了 70%+ 的开销。关键不是看 EXPLAIN 输出的行数,而是看每一步的 cost(PostgreSQL)或 rows × Extra 中的 Using temporary/Using filesort(MySQL)。
- PostgreSQL:重点关注
Plan Rows和Actual Total Time,如果某层Subquery Scan的Actual Total Time明显高于父节点,它就是瓶颈 - MySQL:用
EXPLAIN FORMAT=JSON,搜"select_type": "SUBQUERY"或"DERIVED",看里面的"rows"和"filtered"—— 若rows过大(比如 50 万)且filtered低于 10%,说明没走好索引,子查询结果集膨胀了 - 别只盯着第一行:嵌套越深,执行计划缩进越靠右,但最右边那行未必最重;要顺着
id或select_id把每个子查询分支单独拎出来比耗时
为什么 IN (SELECT ...) 比 JOIN 慢这么多
这不是语法风格问题,是优化器对两种结构的处理逻辑根本不同:IN 子查询在 MySQL 5.6 前默认不重写为半连接,容易触发重复执行;而 JOIN 能利用驱动表 + 被驱动表的索引下推。
- MySQL:当外层条件带
WHERE时,IN (SELECT ...)可能被当作“依赖子查询”(select_type = DEPENDENT SUBQUERY),导致对主表每行都执行一次子查询 - PostgreSQL:
IN会转成Hash Semi Join,但如果子查询返回NULL,语义上要额外过滤,可能退化为Nested Loop - 实操建议:把
IN (SELECT id FROM t2 WHERE ...)改成EXISTS (SELECT 1 FROM t2 WHERE t2.id = t1.id AND ...),或者直接JOIN后DISTINCT—— 不为可读性,只为让优化器选更稳的连接路径
EXPLAIN ANALYZE 显示 Buffers: shared hit=xxx 但还是慢
缓存命中高只说明没刷磁盘,不代表计算不重。尤其嵌套查询中,大量 shared hit 可能来自反复扫描同一张中间结果表(比如 CTE 或派生表),CPU 和内存带宽早被打满了。
- 看
Execution Time和Planning Time分别占比:如果 Planning 占比超 20%,说明查询结构太复杂,优化器在“猜”怎么执行,考虑拆成临时表或物化 CTE(MATERIALIZED) - PostgreSQL 中,CTE 默认不物化,即使写成
WITH t AS (SELECT ...),也可能被内联展开,导致子查询执行多次;加MATERIALIZED强制物化,但要注意内存占用 - MySQL 没 CTE 物化控制,遇到类似场景,老实用
CREATE TEMPORARY TABLE先存住子查询结果,再JOIN—— 多一步写法,少一半重算
嵌套过深时,EXPLAIN 看不到真实执行顺序
执行计划是优化器“预估”的路径,不是运行时真实调用栈。三层以上嵌套(比如 SELECT ... FROM (SELECT ... FROM (SELECT ...)))会让 EXPLAIN 合并显示,掩盖中间层的 I/O 或锁等待。
- 真正要定位卡点,得结合运行时观测:PostgreSQL 开
log_min_duration_statement = 100,查日志里哪段 SQL 实际耗时突增;MySQL 开slow_query_log并设long_query_time=0.1 - 别信“执行计划没 warning 就没问题”—— 常见陷阱是子查询用了函数索引但外层
WHERE条件没对齐,导致索引失效,而EXPLAIN仍显示key=xxx - 最有效的办法:把最内层子查询单独拿出来
EXPLAIN ANALYZE,再把它的输出结果手动代入上一层,模拟执行。这笨,但能暴露优化器“想当然”的地方
嵌套查询的瓶颈从来不在语法嵌套本身,而在每一层输出的数据量、是否可索引、以及优化器有没有把它当成独立单元来调度。看执行计划只是起点,不是终点。










