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

并行查询优化器

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

概述

TDSQL Boundless 的查询优化分两层:
串行优化器(默认使用传统贪心优化器):决定表与表怎么连接、用哪种访问方法(全表扫描 / 索引扫描 / ref 等)、连接顺序如何排列。它产出一棵串行执行计划树
并行查询优化器:在串行计划树之上做"二次优化"——不改连接顺序,而是为这棵已定形的树决定"怎么并行执行最省":哪些表走并行扫描、用多大并行度(DOP)、聚合/排序/limit 是否拆成两阶段、Leader 与 Worker 各做什么。
换句话说:
优化器
回答的问题
输入
输出
串行优化器
这些表怎么连、用什么访问方法最省?
查询块
一棵串行执行计划树
并行查询优化器
这棵树怎么并行执行最省?
串行执行计划树
Leader Slice + Worker Slice 的并行执行计划

并行执行模型

Leader / Worker / Slice

一条并行查询计划被切分为若干 Slice
Leader Slice(顶层):在主连接上执行,DOP 恒为 1,负责汇总、做无法下推的最终算子。
Worker Slice:由若干 Worker 并行执行同一份 PartialPlan 模板,每个 Worker 处理一部分数据,DOP = Worker 数。
Slice 的边界是 Collector:Worker 产出部分结果,典型的 Gather Collector 把多个 Worker 的流汇聚,上交给 Leader。一个 Collector 把计划切分为上下两片。
说明:
Collector 支持 Gather/Shuffle/Broadcast 等算子,当前版本只有 Gather 启用。

分布类型

并行优化器为每个计划节点选择一种分布类型,描述该节点的数据如何分布到 Worker:
分布类型
含义
DOP
Leader 单点(final_singleton)
只在 Leader 执行,最终汇总点
1
广播(replicated)
每个 Worker 持有全量副本
1
并行扫描(partial_scan)
每个 Worker 扫一片数据(动态范围或分区并行扫描),无重分区
可放大
按分片键分布(hashed)
按分布键重分区,或利用物理分片键就地分布;DOP 取决于分布键与物理分片配置
取决于体系
这些类型决定数据如何流转:Leader 单点需要 Collector 回收;广播等价于每个 Worker 自带全量;并行扫描是本地并行无重分区;按分片键分布则可利用 co-located(同分布)特性避免 shuffle。

两阶段算子

某些算子可以拆成"Worker 做部分、Leader 做剩余"的两阶段形式:
算子
两阶段做法
聚合(GROUP BY)
Worker 各做局部聚合(如局部 SUM/COUNT),Leader 再合并(SUM 各 Worker 的 SUM)
排序(ORDER BY)
Worker 各自局部排序,Leader 端做归并排序合并有序流
LIMIT
Worker 各取 limit 行(offset 清零),Leader 再做 limit+offset
窗口函数
仅当 PARTITION BY 列与数据分布兼容时下推(每个 Worker 处理完整分区)
COUNT(*)
Worker 各 COUNT,Leader SUM
不是所有算子都能两阶段:例如带 DISTINCT 的聚合、不可两阶段的 GROUP_CONCAT / LISTAGG 必须走一阶段(Leader 聚合),因为它们的中间态无法简单合并。

并行代价计算原理

并行优化器为每条候选路径计算代价,自底向上累加。与串行代价相比,并行代价多了三部分并行专属开销。

代价组成

一条并行路径的总代价大致是:
并行代价 = 子算子代价 + 本算子自代价 + 并行专属开销
并行专属开销 = 收集代价(Collector)+ 通信代价(随 DOP² 增长)+ 并行效率折扣

收集代价(Collector)

Collector 把多个 Worker 的流汇聚到 Leader,是并行代价中最核心的部分。
说明:
Collector 支持 Gather/Shuffle/Broadcast 等算子,当前版本只有 Gather 启用。
Gather 由三段组成:
Leader 接收代价:Leader 每接收一行付出固定代价,与总接收行数 N 成正比(按字段数计费)。
Worker 发送代价:每个 Worker 每发送一行付出代价,与单 Worker 行数 N/DOP 成正比(按字段数与行宽字节数计费),再加上子算子的执行代价。
归并排序代价:仅当子路径带序(排序下推)时才产生,Leader 把多个有序流归并,与总行数 N 成正比。

通信代价(抑制过宽并行)

通信代价 = 常数 × DOP²
代价随 DOP 平方增长,作为 all-to-all 通信的保守上界。由于 shuffle 尚未完整支持,这一项用于防止优化器偏向过宽的并行计划——DOP 翻倍,通信代价变四倍,从而抑制盲目增大并行度。

并行效率折扣(小表并行收益低)

并非所有数据都适合并行扫描。对行数少的表,并行扫描收益很小,甚至因为数据倾斜而劣化。并行优化器用一条逻辑斯蒂曲线把有效并行度从"串行级 1/DOP"平滑过渡到"满并行 1.0":
并行效率 = 1/DOP + (1 − 1/DOP) × 逻辑斯蒂函数(去重行数, DOP, 行数缩放因子)
有效 DOP(规划并行度)= DOP × 并行效率 (结果钳制到 ≥ 1)
含义:
去重行数很少时,并行效率趋近 1/DOP(接近串行)——并行几乎无收益。
去重行数充足时,并行效率趋近 1.0(充分并行)。
rows_scale 由会话变量 parallel_plan_efficiency_rows_scale(默认 100)控制:值越大,模型越早认为"行数够多、可以充分并行";设为 0 则禁用该模型(一律按满并行估算)。

路径枚举与选择原理

枚举什么

关键区别:串行优化器枚举的是连接顺序;并行优化器不重新枚举连接顺序,它接受串行已选定的计划树,为每个节点枚举"分布方案 + 是否两阶段 + DOP"。
枚举对象是:每个计划节点的多种并行实现方案。例如一个表扫描节点,可枚举"Leader 单点扫描 / 广播 / 动态范围并行扫描 / 分区并行扫描"几种方案;一个聚合节点可枚举"一阶段 Leader 聚合 / 下推一阶段 / 两阶段"。

枚举内容

节点类型
枚举的并行方案
表扫描
Leader 单点(通常作为基线路径由优化器提议)/ 广播 / 动态范围并行扫描 / 分区并行扫描。后两者受行数与代价阈值剪枝
连接(join)
同分布直接拼接(partition-wise)/ 一侧广播 / 兜底 Collector 到 Leader /(条件满足时)并行 Hash Join
聚合
一阶段 Collector / 下推一阶段(分布键兼容时)/ 两阶段
排序
下推排序 + Leader 归并 / 不下推 Collector
limit
两阶段(Worker 取 limit、Leader 取 limit+offset)
窗口
分布键兼容时下推,否则 Collector

支配剪枝(保留非劣解)

一个计划节点可能枚举出多条候选路径。并行优化器用多维支配比较剪枝:只有当一条路径在所有维度上都不劣于另一条时,才丢弃被支配者;只要有一条路径在某个维度更优,两条就同时保留
比较维度大致包括:
分布类型不同:两者都保留(不同分布各有不可替代的优势)。
是否有 Collector 不同:保留(某些位置——如嵌套循环内层——不能放 Collector)。
分布键的包含关系:更宽的分布键与更精确的分布键各有优势,保留。
hint 命中数:命中多的更优。
强制并行偏好:并行路径优先。
总代价 / 启动代价 / 输出行数:低者优先。

最终选择

枚举完后,从根节点的所有候选中选出最优路径,规则:
1. 命中 hint 的路径优先:若某路径命中的 hint 数最多,则总是选它(hint 覆盖代价比较)。
2. 否则选 总代价最低的路径;若 maximum_slices 开启,则优先选 Collector 最多的路径(最大化 Slice 数)。
3. 若选中路径不含 Collector:说明没有任何并行收益,退回串行执行
最后把选中路径切分为 Leader + Worker Slice 树。顶层 Leader Slice DOP 恒为 1。

与串行优化器的差异

串行优化器(默认传统贪心):枚举连接顺序与访问方法,用单一代价模型选最小代价计划,单线程执行,无并行度 / 分布 / Collector 概念。
维度
串行优化器(传统贪心)
并行查询优化器
输入
查询块
串行优化器产出的计划树
输出
一棵串行执行计划
Leader + Worker Slice 树(两层)
枚举对象
连接顺序 + 访问方法
分布类型 / DOP / 是否两阶段
是否改连接顺序
是(贪心选定)
,复用串行已选定的计划树
代价模型
单一串行代价(CPU + IO)
借用更精细的代价模型,额外叠加 Collector / 通信 / 效率折扣
并行度
无(恒单线程)
有(MANUAL 固定 / AUTO 自动)
分布概念
4种分布类型 + 分布键 + 等价类
剪枝
按代价 + 有序性等维度
按分布类型 / 分布键 / Collector / DOP 源 + 代价多维
选择策略
选最小代价计划
hint 优先,其次最小代价,无 Collector 则退回串行
触发时机
计划生成主路径
串行计划之后的并行化后处理
可退回串行
是,多重门控,可能枚举出并行计划却最终退回串行
最根本的差异:串行优化器决定"怎么连",并行优化器决定"这棵树怎么并行执行"——两者解耦,连接顺序决策完全在串行阶段完成。

用户控制

这是本文重点。用户通过会话变量SQL hint 两类手段控制并行优化器。

启用并行查询

并行查询默认关闭。最简启用:
-- 开启并行,固定 DOP=8(MANUAL 模式)
SET SESSION max_parallel_degree = 8;

-- 或让优化器自动选 DOP
SET SESSION max_parallel_degree = 16; -- AUTO 下的 DOP 上限
SET SESSION parallel_degree_policy = AUTO;
说明:
max_parallel_degree = 0 表示关闭并行;> 0 表示开启。AUTO 模式下它作为 DOP 上限。

核心会话变量

变量
默认
作用
max_parallel_degree
0(关闭)
并行度。MANUAL 下即固定 DOP;AUTO 下为 DOP 上限
parallel_degree_policy
MANUAL
DOP 策略:MANUAL(固定)/ AUTO(自动)
parallel_plan_cost_threshold
50000
全局门控:串行代价 ≥ 此值才考虑并行
parallel_scan_cost_threshold
10000
MANUAL:表串行代价低于此值不走并行扫描;AUTO:每 Worker 扫描代价达标线
parallel_scan_records_threshold
10000
表预估访问行数 < 此值不走并行扫描
parallel_scan_ranges_threshold
2
预估范围数 < 此值不走并行扫描
parallel_plan_efficiency_rows_scale
100
并行效率折算的行数缩放因子;0禁用效率模型
parallel_plan_compare_serial_cost
false
为真时,允许把"不并行(原串行计划)"纳入候选与并行计划比代价
parallel_query_switch
默认子集
位掩码总开关
tdsql_enable_parallel_hash_join
false
开启并行 Hash Join

parallel_query_switch 位掩码

parallel_query_switch 是一组开关的位掩码,用 开关名=on/off 设置:
SET SESSION parallel_query_switch = 'force=on,subquery_pushdown=on';
完整选项表见 并行系统变量

PARALLEL hint

PARALLEL hint 控制单个表 / 查询块 / 全局的并行度与扫描类型,有三层范围(表级 > 查询块级 > 全局,优先级递减)。完整语法详见 优化器 Hints
-- 表级:指定该表用 DOP=4
SELECT /*+ PARALLEL(t 4) */ COUNT(*) FROM t;

-- 表级 + AUTO:该表自动选 DOP
SELECT /*+ PARALLEL(t AUTO) */ COUNT(*) FROM t;

-- 表级禁用该表并行
SELECT /*+ PARALLEL(t 0) */ ... FROM t;

-- 全局:整个查询用 DOP=8
SELECT /*+ PARALLEL(8) */ ...;

-- 指定扫描类型
SELECT /*+ PARALLEL(t 4 DYNAMIC_RANGE) */ ... FROM t; -- 动态范围并行扫描
SELECT /*+ PARALLEL(t 4 PARTITION) */ ... FROM t; -- 分区并行扫描
要点:
hint 命中的路径会被加权,选优时优先于代价最低的路径。
显式指定 DOP 或 force 时,会跳过行数 / 代价阈值检查——即"用户明确要求并行,就不再因阈值剪枝"。
PARALLEL(t AUTO) 在该表粒度强制 AUTO,覆盖会话变量。
PARALLEL(t 0) 显式禁用该表并行。

PQ_DISTRIBUTE hint

PQ_DISTRIBUTE 控制聚合 / 排序 / 窗口 / 表连接的分布类型。它不是直接选路径,而是给对应分布类型的路径加权。完整语法详见 优化器 Hints
-- 两阶段聚合:Worker 局部聚合 + Leader 合并
SELECT /*+ PQ_DISTRIBUTE(@qb AGGREGATE NONE GATHER) */ ...
FROM /*+ QB_NAME(qb) */ ...;

-- 一阶段聚合(gather 到 Leader)
SELECT /*+ PQ_DISTRIBUTE(@qb AGGREGATE GATHER) */ ...;

-- 排序下推(Worker 排序 + Leader 归并)
SELECT /*+ PQ_DISTRIBUTE(@qb SORT NONE GATHER) */ ...;
target 取 TABLE / AGGREGATE / SORT / WINDOW,每个 target 的 first/second 类型取 NONE / GATHER / HASH / BROADCAST / UNSPECIFIED。常见组合:
形态
target
first
second
两阶段聚合
AGGREGATE
NONE
GATHER
一阶段聚合
AGGREGATE
GATHER
UNSPECIFIED
下推排序
SORT
NONE
GATHER
下推一阶段聚合
AGGREGATE
NONE
UNSPECIFIED

强制并行与退回串行

两个机制决定"是否真的走并行":
forceparallel_query_switch)或 PARALLEL hint:强制尽量走并行,跳过阈值检查,选优时偏好并行路径。
parallel_plan_compare_serial_cost:为真时,把"不并行(原串行计划)"也作为合法候选,与并行计划比代价。为假(默认)时,无 Gather 的纯串行路径不参与竞争。
但即使 force,若最终选中的路径不含 Collector,仍会退回串行——因为没有任何并行收益。即:force 让路径免于阈值剪枝并优先选中带 hint 的并行路径,但不会强行选择一个毫无并行结构的计划。

观察手段

执行计划与 optimizer trace 的完整解读详见 并行计划解读。简要说明:
EXPLAIN:可看到并行计划中的 Collector / 并行扫描 / 分区扫描节点,以及各 Slice 的 DOP。若 EXPLAIN 里没有 Collector 等并行节点,说明该查询未走并行。
optimizer_trace:并行优化输出在 PARALLEL_PLAN_OPTIMIZATION 段,可看到候选路径、剪枝原因、Auto-DOP 计算结果与中止原因、最终 resultno_valid_parallel_plan / best_plan_is_serial / 正常选中)。