帮你快速理解、总结文档立即下载

并行查询子查询

最近更新时间:2026-08-20 15:23:31
我的收藏

概述

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 个 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

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.a
FROM t1
WHERE 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;