一、概述
| 策略 | 执行位置 | 适用场景 | 核心优势 |
| 预执行(Pre-evaluation) | Leader 节点(协调节点) | 结果可缓存、内部已启用并行查询的子查询 | 避免 Worker 节点嵌套执行并行计划 |
| 下推(Inline Evaluation) | 各 Worker 节点(执行节点) | 表达式安全、无嵌套并行的子查询 | 子查询在各 Worker 中并行执行,减少 Leader 节点瓶颈 |
判定原则:
·若子查询内部已生成并行计划,则优先采用预执行(Worker 节点不能嵌套执行并行计划)。
·若子查询整体表达式安全且不含嵌套并行,则可采用下推。
两条路径均不满足时,子查询以串行方式执行。
这篇仅讨论子查询在并行查询中的执行策略,不涉及非并行场景下的子查询优化。
二、前置条件
已开启并行查询功能(SET max_parallel_degree > 0)
三、示例环境
以下示例使用相同的测试表和并行查询配置。
CREATE TABLE t1 (a INT, b INT);CREATE TABLE t2 (a INT, b INT);INSERT INTO t1 VALUES (1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10);INSERT INTO t2 VALUES (3,3),(5,5),(7,7),(9,9);SET max_parallel_degree=4;SET parallel_query_switch='force=on';
四、例1:子查询预执行
EXPLAIN FORMAT=TREE SELECT * FROM t1 WHERE a = (SELECT MAX(b) FROM t2);
-> Gather (slice: 1, workers: 4) [1] 外层并行:4 个 Worker Pre-evaluated subqueries: select #2 [2] 预执行标记:子查询已在 Leader 执行并缓存 -> Filter: (t1.a = (select #2)) [3] Worker 使用缓存结果 -> Table scan on t1, with range parallel scan [4] Worker 并行扫描 -> Select #2 (subquery in condition; run only once) [5] 缓存结果仅执行一次 -> Aggregate: max(t2.b) -> Gather (slice: 1, workers: 4) [6] 子查询内部同样启用了并行查询 -> Aggregate: max(t2.b) -> Table scan on t2, with range parallel scan
关键解读:
·Pre-evaluated subqueries 是预执行路径的识别信号——Leader 在分发前已完成子查询执行并缓存结果。
·run only once 表示子查询结果已缓存,Worker 直接复用,不会重复执行。
·子查询内部的 Gather 说明预执行时子查询本身同样启用了并行执行,这是预执行的核心优势:子查询可并行执行,且结果只计算一次。
五、例2:子查询下推
EXPLAIN FORMAT=TREE SELECT * FROM t1 WHERE a = (SELECT /*+ subquery(pq_inline_evaluation) */ MAX(b) FROM t2);
-> Gather (slice: 1, workers: 4) [1] 外层并行:4 个 Worker -> Filter: (t1.a = (select #2)) [2] 无 Pre-evaluated subqueries 标记 -> Table scan on t1, with range parallel scan [3] Worker 并行扫描 -> Select #2 (subquery in condition; run only once) [4] 各 Worker 分别独立执行一次 -> Aggregate: max(t2.b) -> Table scan on t2 [5] 子查询内部无 Gather,Worker 内以串行方式执行
关键解读:
·无 Pre-evaluated subqueries 是下推路径的识别信号——子查询未被 Leader 预执行,而是下推至各 Worker 执行。
·子查询内部无 Gather,说明子查询在各 Worker 内以串行方式执行(Worker 不能嵌套并行)。
·run only once 在下推路径中含义不同:表示该子查询在每个 Worker 的上下文中仅执行一次,不随外层行变化而重复执行。
预执行 vs 下推 对比:
| 差异点 | 预执行 | 下推 |
| Pre-evaluated subqueries | ✅ 有 | ❌ 无 |
| 子查询内部 Gather | ✅ 可并行执行 | ❌ Worker 内以串行方式执行 |
| 子查询执行次数 | Leader 执行1次,Worker 复用 | 各 Worker 分别执行1次 |
| 适用场景 | 子查询内部可并行执行、结果可缓存 | 子查询简单、避免 Leader 节点瓶颈 |
六、例3:派生表物化(Derived Table Materialization)
派生表的预执行/下推机制与子查询一致,区别在于识别标记为 Pre-evaluated derived tables。
预执行路径:
— 禁用派生表合并,使派生表保留为独立物化表SET optimizer_switch='derived_merge=off';EXPLAIN FORMAT=TREE SELECT * FROM t1, (SELECT a FROM t2) AS d WHERE t1.a = d.a;
-> Gather (slice: 1, workers: 4) [1] 外层并行:4 个 Worker Pre-evaluated derived tables: d [2] 预执行标记:派生表 d 已在 Leader 物化 -> Inner hash join (t1.a = d.a) -> Table scan on t1, with range parallel scan [3] Worker 并行扫描 -> Hash -> Table scan on d -> Materialize [4] 物化算子 -> Gather (slice: 1, workers: 4) [5] 物化过程本身采用并行执行 -> Table scan on t2, with range parallel scan
关键解读:
·Pre-evaluated derived tables: d 是派生表预执行的识别信号。
·物化过程中的 Gather 说明 Leader 在预物化阶段采用并行执行,物化完成后 Worker 共享结果。
下推路径:
EXPLAIN FORMAT=TREE SELECT /*+ NO_MERGE(t) */ t1.a FROM t1, (SELECT /*+ subquery(pq_inline_evaluation) */ a FROM t2) t WHERE t1.a = t.a;
-> Gather (slice: 1, workers: 4) [1] 外层并行:4 个 Worker -> Inner hash join (t1.a = t.a) [2] 无 Pre-evaluated derived tables 标记 -> Table scan on t1, with range parallel scan [3] Worker 并行扫描 -> Hash -> Table scan on t -> Materialize [4] 物化操作位于 Worker 侧执行计划中 -> Table scan on t2 [5] Worker 内以串行方式扫描
关键解读:
·无 Pre-evaluated derived tables 是下推路径的识别信号。
·Materialize 位于 Worker 侧执行计划中,表示派生表未由 Leader 预先物化并缓存。
·物化过程中无 Gather,Worker 内以串行方式物化。
七、相关 Hint
| Hint | 作用 |
| subquery(materialization) | 强制 IN 子查询采用物化策略 |
| subquery(materialization, pq_inline_evaluation) | 强制 IN 物化子查询采用内联评估策略,即下推执行,不经 Leader 预执行 |
| subquery(pq_inline_evaluation) | 指定子查询采用内联评估策略,即将子查询保留在 Worker 执行计划中,不由 Leader 预执行 |
| PARALLEL(N) | 指定查询块并行度 |
| PARALLEL(0) | 禁用指定查询块的并行查询,避免子查询内部生成并行计划;优化器可据此选择预执行或下推策略 |
| NO_MERGE(table) | 禁止指定派生表被合并,使其保留为独立物化表 |
八、Hint 组合使用示例
示例1:强制 IN 子查询采用物化且下推(不经预执行)
EXPLAIN FORMAT=TREE SELECT t1.aFROM t1WHERE t1.a IN ( SELECT /*+ subquery(materialization, pq_inline_evaluation) */ t2.a FROM t2 LIMIT 10);
-> Gather (slice: 1, workers: 4) [1] 外层并行:4 个 Worker -> Nested loop inner join -> Filter: (t1.a is not null) -> Table scan on t1, with range parallel scan [2] Worker 并行扫描 -> Single-row index lookup on <subquery2> using <auto_distinct_key> (a=t1.a) -> Materialize with deduplication [3] 子查询物化去重 -> Filter: (`<temporary>`.a is not null) -> Table scan on <temporary> -> Materialize [4] 物化操作位于 Worker 侧执行计划中 -> Limit: 10 row(s) -> Table scan on t2 [5] 子查询内部无 Gather,Worker 内以串行方式扫描
关键解读:
·无 Pre-evaluated subqueries 标记,表明子查询未被 Leader 预执行,而是下推至各 Worker 执行。
·Materialize 位于 Worker 侧执行计划中,表示未由 Leader 预先物化并缓存。
·子查询内部无 Gather,说明各 Worker 内子查询以串行方式执行(Worker 不能嵌套并行)。
示例2:子查询内部禁用并行查询,由 Leader 预执行
EXPLAIN FORMAT=TREE SELECT a FROM t2 WHERE b IN ( SELECT /*+ PARALLEL(0) subquery(materialization) */ a FROM t1);
-> Gather (slice: 1, workers: 4) [1] 外层并行:4 个 Worker Pre-evaluated subqueries: select #2 [2] 预执行标记:子查询已在 Leader 执行并缓存 -> Filter: <in_optimizer>(t2.b,t2.b in (select #2)) [3] Worker 使用缓存结果 -> Table scan on t2, with range parallel scan [4] Worker 并行扫描 -> Select #2 (subquery in condition; run only once) [5] 缓存结果仅执行一次 -> Filter: ((t2.b = `<materialized_subquery>`.a)) -> Limit: 1 row(s) -> Index lookup on <materialized_subquery> using <auto_distinct_key> (a=t2.b) -> Materialize with deduplication [6] 子查询内部无 Gather,因 PARALLEL(0) 禁用了并行查询 -> Table scan on t1
关键解读:
·Pre-evaluated subqueries 是预执行路径的识别信号——Leader 在分发前已完成子查询执行并缓存结果。
·run only once 表示子查询结果已缓存,Worker 直接复用,不会重复执行。
·子查询内部无 Gather,因 PARALLEL(0) 禁用了子查询内部的并行查询,Leader 以串行方式完成物化后供 Worker 共享。
九、Session 级开关
| 参数或开关 | 作用 |
| max_parallel_degree | 控制最大并行度,设置为大于0的值表示开启并行查询,设置为0表示关闭并行查询 |
| parallel_query_switch='force=on' | 强制优化器选择并行计划;当未自动选择并行计划时可使用该开关 |
| parallel_query_switch='subquery_pushdown=off' | 关闭子查询下推策略 |
| parallel_query_switch='subquery_pre_evaluation=off' | 关闭子查询预执行策略 |
— 开启并行查询SET max_parallel_degree = 4;– 强制选择并行计划SET parallel_query_switch = 'force=on';– 关闭子查询下推SET parallel_query_switch = 'subquery_pushdown=off';– 关闭子查询预执行SET parallel_query_switch = 'subquery_pre_evaluation=off';– 恢复 parallel_query_switch 为默认值,具体默认值以当前版本为准SET parallel_query_switch = DEFAULT;– 关闭并行查询SET max_parallel_degree = 0;