数据库-GaussDB-基础篇-典型SQL调优点-语句下推调优

GaussDB分布式|语句下推调优完整梳理(官方文档 + 实操个人观点)

webp (2)

{{{width=“auto” height=“auto”}}}

一、语句下推基础介绍

GaussDB分布式架构由CN协调节点和DN数据节点组成。业务端JDBC连接只对接CN节点,CN负责接收SQL、解析生成执行计划,DN负责存储真实业务数据。

内核在分布式场景下,一共有3种执行计划模式:

  1. 下推语句计划(最优):CN直接把完整SQL下发到DN,DN本地独立完成全部计算,只把最终结果返回CN。这是SQL调优追求的目标,减少跨节点数据传输,性能最好。

  2. 分布式执行计划:CN先完成SQL编译、逻辑优化,生成执行计划树,再将算子下发到DN执行。

  3. 发送语句的分布式执行计划(兜底策略):前两种无法使用时触发。能下推的简单过滤、扫描下发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 改写,不要强行封装函数。

六、整体调优总结(实操观点)

1. 语句下推的核心目标:把计算下压到 DN,CN 只负责接收最终结果,减少数据搬运;

2. 不要只看Data Node Scan就判定 SQL 已经完全下推,必须通读完整执行计划,排查 Streaming、CN 上的聚合 / 排序算子;

3. 多表 Join 的下推能力,根源在建表时的分布键设计,属于前置设计,不是后期 SQL 简单改写就能解决;

4. Recursive CTE、VOLATILE 函数、returning、UNION(去重)都是典型下推黑名单,写 SQL 阶段就要规避;

5. 自定义函数优先 IMMUTABLE,函数属性定义错误,会直接毁掉 SQL 下推能力;

6. 执行计划出现大量 Streaming,代表跨节点数据重分布,属于高危 SQL,需要优先优化。

posted @ 2026-09-27 12:29  打印helloworld  阅读(3)  评论(0)    收藏  举报