数据库-GaussDB-基础篇-典型SQL调优点-语句下推调优
GaussDB分布式|语句下推调优完整梳理(官方文档 + 实操个人观点)

{{{width=“auto” height=“auto”}}}
一、语句下推基础介绍
GaussDB分布式架构由CN协调节点和DN数据节点组成。业务端JDBC连接只对接CN节点,CN负责接收SQL、解析生成执行计划,DN负责存储真实业务数据。
内核在分布式场景下,一共有3种执行计划模式:
-
下推语句计划(最优):CN直接把完整SQL下发到DN,DN本地独立完成全部计算,只把最终结果返回CN。这是SQL调优追求的目标,减少跨节点数据传输,性能最好。
-
分布式执行计划:CN先完成SQL编译、逻辑优化,生成执行计划树,再将算子下发到DN执行。
-
发送语句的分布式执行计划(兜底策略):前两种无法使用时触发。能下推的简单过滤、扫描下发DN,DN把中间结果回传给CN;剩余无法下推的计算逻辑全部在CN上执行。
【个人理解】
第三种模式是最需要警惕的。DN只返回中间数据,大量数据在DN→CN之间网络传输,CN会承担聚合、排序、关联等 heavy 计算,极易出现CN CPU、内存、带宽打满,成为整个集群瓶颈。我们做SQL优化,核心目标就是尽量规避该兜底模式。
-
语句无法下推的根本原因:SQL中包含内核不支持下推的语法、函数。大部分场景可以通过改写SQL、调整函数属性实现下推。
-
开启语句下推能力,需要设置GUC参数:
enable_fast_query_shipping = on。通过
explain查看执行计划,执行计划最外层出现Data Node Scan,代表语句下发到DN执行。
【重点踩坑观点】
- 这是实操最容易踩的误区:****出现Data Node Scan ≠ 全部算子都下推到DN。
Data Node Scan仅代表CN把SQL文本发给DN;像聚合、排序这类算子,依然有可能在CN执行。必须完整看整个执行计划树,不能只看这一个算子就判定完全下推。
二、下推语句计划典型场景 & 判定规则
1. 单表查询语句下推
-
判断核心: CN是否需要对DN返回的结果集做二次计算。
-
✅可下推: 简单单表过滤,无需CN二次计算。
select * from t where c1 > 1;
-
❌不可下推: SQL 需要 CN 二次处理结果,例如
limit/offset、全局聚合、窗口函数、distinct、全局排序。✅可下推示例:
explain select * from customer1 where c_custkey < 1000;
执行计划会出现Data Node Scan,where 过滤在 DN 本地完成,仅返回少量结果集。
❌不可下推示例:
explain verbose select count(c_custkey order by c_custkey) from customer1;
聚合函数内部嵌套 order by,该逻辑无法下推,DN 只把原始数据返回 CN,CN 端完成聚合排序。
2. 多表 Join 查询
-
核心规则: Join 条件与表的 hash 分布列匹配,则 Join 可以下推到 DN 本地执行;不匹配则无法下推。
-
原理: 关联两张分布式表的 hash 分布键,相同 Join 值的数据天然落在同一个 DN,Join 在 DN 本地完成,不需要跨节点搬迁数据。
-
补充: 优化器会自动评估代价。即使满足分布条件,若预估数据量很小,优化器可能选择把全量数据拉到 CN 做 Join;业务数据增长后,该执行计划容易性能恶化,是 SQL 优化重点关注项。
-
✅可下推示例: 两张表分布键为 custkey,join 条件使用分布键
explain select * from t1 join t2 on t1.custkey = t2.custkey;
- ❌不可下推示例: join 条件不是分布键,出现 Streaming 重分布算子
explain verbose select * from t1,t2 where t1.b = t2.b;
执行计划会看到Streaming(REDISTRIBUTE),数据跨节点搬运,关联计算在 CN 执行。
【个人理解】
- 分布式 Join 的本质: 同 DN 内的数据做本地 Join 才可以下推;数据分散在不同 DN,必须先把数据拉到一处再关联,就失去下推价值。建表时分布键设计,直接决定后续 SQL 能不能下推。
3. UNION / UNION ALL / INTERSECT / EXCEPT
-
规则: 左右两边分支都能下推,并且两边分支数据落在同一个 DN,整个集合运算才能整体下推。
-
UNION ALL:无去重,仅合并数据,分支数据在同一个 DN,则 union 可下推;
-
UNION:需要全局去重,大概率无法整体下推。
explain select id from t1 union all select id from t2;
UNION ALL: 允许下推,多个分支在 DN 分别执行,结果直接返回 CN;
explain select id from t1 union select id from t2;
UNION(不带 ALL,需要去重): 去重逻辑 CN 完成,一般不能完整下推。
4. CTE / Recursive CTE(递归 CTE)场景
- CTE 能否下推有一套明确规则:
1. 普通 CTE:满足条件可以下推;
2. Recursive CTE(递归 CTE)限制非常多,绝大多数场景无法下推。
WITH RECURSIVE 不支持下推的场景:
| 序号 | 场景 | 不下推原因 |
|---|---|---|
| 1 | 包含外表的查询场景 | LOG: SQL can't be shipped, reason: RecursiveUnion contains ForeignScan is not shippable,递归CTE包含外表,当前版本暂不支持下推 |
| 2 | 多 nodegroup 场景 | LOG: SQL can't be shipped, reason: With-Recursive under multi-nodegroup scenario is not shippable,多 nodegroup 下,基表存储 nodegroup 不相同,或者计算 nodegroup 不一致,当前版本暂不支持下推 |
| 3 | UNION 不带 ALL,需要去重 | LOG: SQL can't be shipped, reason: With-Recursive does not contain 'ALL' to bind recursive & non-recursive branches,UNION 不带 ALL 需要全局去重,递归分支与非递归分支无法在 DN 完成去重 |
| 4 | 基表中包含系统表 | LOG: SQL can't be shipped, reason: With-Recursive contains system table is not shippable,递归 CTE 引用系统表,系统表仅 CN 可访问,不能下发 DN |
| 5 | 基表扫描只有 VALUES 子句,仅在 CN 上即可完成执行 | LOG: SQL can't be shipped, reason: With-Recursive contains only values rte is not shippable,仅 VALUES 常量,不需要访问 DN,仅 CN 执行,无法下发 DN |
| 6 | 相关查询的关联条件仅在递归部分,非递归部分无关联条件 | LOG: SQL can't be shipped, reason: With-Recursive recursive term correlated only is not shippable,关联条件只存在递归分支,非递归分支没有关联条件,无法下推 |
| 7 | 非递归部分 limit 为 Replicate 计划,递归部分为 Hash 计划,计划存在冲突 | LOG: SQL can't be shipped, reason: With-Recursive contains conflict distribution in non-recursive(Replicate) recursive(Hash),两个分支数据分布策略不一致,计划冲突,无法整体下推 |
| 8 | 多层 Recursive 嵌套,即 recursive 的递归部分又嵌套另一个 recursive 查询 | LOG: SQL can't be shipped, reason: Recursive CTE references recursive CTE "cte",递归 CTE 内部再嵌套递归 CTE,无法拆解下发 DN |
【个人理解】
递归 CTE 的迭代逻辑在 CN 很难拆解到 DN 并行执行,内核限制多。业务中遇到递归树形查询,优先评估改写方式;如果无法改写,要提前预估 CN 计算压力。
三、查看分布式执行计划是否下推
- 两种判断手段:
1. 前置参数:enable_fast_query_shipping = on,开启语句下推框架;
2. 看 explain 执行计划算子:
-
计划最外层
Data Node Scan:语句下发 DN; -
计划出现
Streaming算子:数据需要跨节点重分布,不能完整下推。
【个人理解】 Streaming 算子是一个强烈的信号: 一旦看到 Streaming,代表节点之间在搬运数据,大概率 SQL 落在兜底模式,需要优化。
四、不支持下推的语法 + 示例
- 下面这些语法,会直接导致语句无法下推:
1. returning子句
explain update customer1 set c_name = 'a' returning c_name;
2. 聚合函数内嵌套order by
explain verbose select count (c_custkey order by c_custkey) from customer1;
3. count(distinct)字段不支持重分布场景
explain verbose select count(distinct b) from test_stream;
4. array 数组表达式
explain verbose select array[c_custkey] from customer1 order by c_custkey;
5. 部分数据类型:stream 流类型、float 部分场景不支持分布;
6. 带WITH Recursive CTE、部分普通 CTE、相关子查询语法。
五、不支持下推的函数
- GaussDB 自定义函数有 3 种易变性属性,直接决定函数是否支持下推:
1. IMMUTABLE:相同输入,永远返回相同结果,支持下推;
2. STABLE:同一次扫描内返回值不变,满足proshippable=true才可以下推;
3. VOLATILE:同一扫描内结果会变化,默认不可下推,例如random()。
-
两个关键属性:
provolatile(易变性)、proshippable(是否可分发)。 -
查询函数属性: 查看系统表
pg_proc的provolatile、proshippable字段。
【个人理解 + 实例观点】
业务自定义函数是高频踩坑点。
如果函数输入确定、输出固定,一定要定义为 IMMUTABLE。
❌ 无法下推版本(VOLATILE)
CREATE FUNCTION func_percent_2 (NUMERIC, NUMERIC) RETURNS NUMERIC
AS 'SELECT $1 / $2 WHERE $2 > 0.01'
LANGUAGE SQL
VOLATILE;
SELECT func_percent_2(ss_sales_price, ss_list_price) FROM store_sales;
✅ 修改为 IMMUTABLE,支持下推
CREATE FUNCTION func_percent_1 (NUMERIC, NUMERIC) RETURNS NUMERIC
AS 'SELECT $1 / $2 WHERE $2 > 0.01'
LANGUAGE SQL
IMMUTABLE;
SELECT func_percent_1(ss_sales_price, ss_list_price) FROM store_sales;
系统内置不可下推函数,如
random()、exec_hadoop_sql,优先寻找等价 SQL 改写,不要强行封装函数。

浙公网安备 33010602011771号