关联子查询天然排斥并行执行,因其依赖外层每行触发独立执行,与数据库并行机制根本冲突;必须改写为JOIN+窗口函数、物化CTE或非关联子查询才能启用并行。

关联子查询天然排斥并行执行
数据库的并行查询机制(如 PostgreSQL 的 parallel_seq_scan、MySQL 8.0+ 的 Parallel Query)设计目标是“对单一大表做分片扫描”,而关联子查询(correlated subquery)本质是“外层每行触发一次独立子查询执行”,二者在执行模型上根本冲突。优化器看到 WHERE score > (SELECT AVG(score) FROM exam e2 WHERE e2.dept = e1.dept) 这类结构,第一反应是嵌套循环,不是并行——因为子查询依赖外部行值,无法提前切分任务。
常见错误现象:EXPLAIN ANALYZE 显示 Workers Planned: 4,但实际只有主进程在跑,所有 worker 都处于 WaitEventIO 或空闲状态;或者子查询部分始终标记为 SubPlan 节点,且无任何并行扫描痕迹。
- PostgreSQL 不会对 SubPlan 启用并行,哪怕你设了
max_parallel_workers_per_gather = 8 - SQL Server 的并行优化器会直接跳过含
DEPENDENT SUBQUERY的分支,改走串行 Nested Loop - MySQL 的 Parallel Query 完全不识别关联子查询语法,
/*+ PARALLEL(4) */提示会被静默忽略
并行资源被子查询的重复调用吃光
即使强行让某一层“看起来并行”(比如外层表扫描并行了),关联子查询仍会在每个 worker 内部重复执行——不是 4 个线程共跑 1 次子查询,而是每个线程各自跑 N 次。假设外层有 10 万行、启用了 4 个 worker,子查询实际执行次数仍是 10 万次(非 2.5 万次),且每次都在各自内存空间重建执行上下文、解析 SQL、查索引、聚合——CPU 消耗翻倍,还加剧 cache line 争抢。
性能影响比单线程更差:你看到 top 里 CPU 利用率冲到 100%,但真实吞吐没涨,反因线程同步开销导致 context switch/sec 暴增,perf record -e sched:sched_switch 可验证。
- 子查询中若含聚合(如
AVG()、COUNT()),每次都要建临时哈希表,worker 间无法共享,内存碎片飙升 - 当子查询涉及多表 JOIN,每个 worker 都要独立完成一遍嵌套循环,I/O 请求呈线性放大
- 没有全局结果缓存机制,相同
e1.dept值被反复计算,统计信息再准也救不了这个架构缺陷
替代方案必须绕开“逐行触发”语义
想真正利用多核,就得把“每行都算一次”的逻辑,改成“一次性算完再匹配”。这不是加 hint 能解决的,必须重构 SQL 语义。
可落地的改写方式:
- 用
JOIN+ 窗口函数替代:把(SELECT AVG(score) FROM exam e2 WHERE e2.dept = e1.dept)改成AVG(score) OVER (PARTITION BY dept),再与主表JOIN,此时整个窗口计算可被并行化 - 用物化 CTE 预计算:PostgreSQL 中写
WITH dept_avg AS (SELECT dept, AVG(score) AS avg_score FROM exam GROUP BY dept) SELECT ... FROM main_table m JOIN dept_avg d ON m.dept = d.dept,CTE 结果集可被并行扫描 - MySQL 8.0+ 若必须保留子查询形态,至少确保它变成非关联的:先
CREATE TEMPORARY TABLE _dept_avg AS SELECT dept, AVG(score) FROM exam GROUP BY dept,再在主查询里用(SELECT avg_score FROM _dept_avg WHERE dept = e1.dept)——此时子查询退化为等值查找,可能触发索引+缓存
统计信息不准会让并行尝试彻底失效
即使你成功把关联子查询改写成可并行形式(如 CTE + JOIN),如果 exam 表的统计信息过期,优化器仍可能拒绝并行:它预估子查询结果集太大(比如认为 GROUP BY dept 会产出 10 万行),就判定并行分发代价高于收益,强制回落到串行 HashAggregate。
关键检查点:
- 运行
ANALYZE exam(PostgreSQL)或ANALYZE TABLE exam(MySQL),确认pg_stats.n_distinct或INFORMATION_SCHEMA.STATISTICS.cardinality与实际COUNT(DISTINCT dept)偏差不超过 2 倍 - 对
dept字段单独建直方图(MySQL 8.0+)或扩展统计(PostgreSQLCREATE STATISTICS),让优化器看清数据倾斜程度 - 避免在 CTE 中使用
LIMIT或ORDER BY,这会切断并行下推路径
真正卡住并行的,从来不是 CPU 核数或配置参数,而是 SQL 写法把“可并行”变成了“必须串行”——只要还带着 WHERE ... = outer.col 这种绑定,就别指望多核能帮上忙。

















