概述
TDSQL Boundless 并行查询对子查询提供了两种互补执行策略:预执行(Pre-evaluation)和下推(Inline Evaluation)。
策略 | 执行位置 | 适用场景 | 核心优势 |
预执行(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 个 WorkerPre-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 个 WorkerPre-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
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 个 WorkerPre-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;