数据库-GaussDB-基础篇之子查询调优

GaussDB 子查询调优详解

一、子查询背景介绍

业务应用在操作数据库时,会大量使用子查询。相比直接多表 JOIN 写法,子查询在语义上更加独立完整,逻辑思路清晰,在复杂 SQL 场景下可读性更强,因此被广泛使用。

在 GaussDB 内核的 SQL 解析阶段,会根据子查询所在位置,将其分为 SubQuery(子查询) 和 SubLink(子链接) 两大类:

  1. SubQuery(子查询)
    对应查询解析树中的RangeTblEntry。
    通俗理解:写在FROM后面独立的SELECT语句,也就是派生表。
    本质:范围表,当作一张临时表参与 JOIN 运算。
  2. SubLink(子链接)
    对应查询解析树中的表达式节点。
    通俗理解:写在WHERE/ON条件、targetlist(SELECT返回字段)里面的子查询。
    本质:表达式,不是表,作用是返回一个值。

表格

SubLink 类型 对应 SQL 语法 说明
exist_sublink EXISTS / NOT EXISTS 存在性判断,优化器重点优化,支持子链接提升
any_sublink ANY(子查询)、IN(子查询) 支持< > =运算符;IN / NOT IN (select ...)归为此类
all_sublink ALL(子查询) 支持< > =运算符
rowcompare_sublink record op (select ...) 行比较子链接
expr_sublink (SELECT single target item ...) 标量子查询 SSQ,返回单行单列
array_sublink ARRAY(select ...) 数组子链接
cte_sublink WITH query(...) CTE 公共表表达式

优化说明:

  • exist_sublink、any_sublink:GaussDB 优化器针对这两类做了专项优化,支持子链接提升(Sublink Release);
  • expr_sublink:部分场景支持提升,但使用灵活,极易产生性能问题;可通过 GUC 参数rewrite_rule关闭该类子链接的提升优化。

子查询两大类别

  1. 非相关子查询(None-Correlated SubQuery)
    子查询执行不依赖外层查询任何字段,具备独立性,可以提前一次性求解,子查询计划优先于外层查询执行。
select t1.c1,t1.c2
from t1
where t1.c1 in (
    select c2
    from t2
    where t2.c2 IN (2,3,4)
);

执行计划示例:Hash Right Semi Join,一次性算出子查询全部结果集合。

  1. 相关子查询(Correlated SubQuery)
    子查询引用外层表字段,无法独立执行;外层表每扫描一行,就需要带入外层字段值执行一次子查询。
select t1.c1,t1.c2
from t1
where t1.c1 in (
    select c2
    from t2
    where t2.c1=t1.c1 AND t2.c2 IN (2,3,4)
);

性能风险:无法提升时,执行计划生成SubPlan + Streaming(BROADCAST),内层表数据量大时性能急剧恶化。

专有名词

  • Sublink 提升(Release / 子链接提升):内核将表达式形式的 SubLink,重写转换为 Join(半连接 / 外连接),消除 SubPlan,不再循环执行子查询,规避 DN 之间广播数据,是子查询调优的核心目标。
  • SSQ:ScalarSubQuery,标量子查询,属于expr_sublink,返回单行单列标量值。
  • CSSQ:Correlated-ScalarSubQuery,包含相关条件的标量子查询。

满足以下条件,优化器可自动把子链接改写为 JOIN,消除 SubPlan:

  1. IN any_sublink(非相关子查询)
    子查询不引用外层表列,无易变函数,可提升为Nested Loop Semi Join。
  2. EXISTS exist_sublink 等值相关子查询
    WHERE 条件存在外层表等值关联;子查询其余部分不能再引用外层表。
    限制条件:必须包含 from 子句;不能带 with、聚集函数、group by、limit、窗口函数、having、易变函数;提升为Hash Semi Join。
  3. 带聚集函数的等值相关子查询
  • where 条件:外层表等值关联,多个条件仅允许AND连接;
  • select 输出:仅允许 1 列,聚合函数只能使用 max/min 等,不能使用 count;聚合参数不能来自外层表;
  • 禁止 group by、having、集合操作;仅支持 Inner Join。
  1. WHERE OR 子句中的 EXISTS 相关 SubLink
    WHERE 条件通过 OR 连接多个 EXISTS 相关子查询,满足条件可逐步改写为左连接,实现提升。
  2. TargetList 返回列中的 SSQ 标量子查询(不带 count)
    SELECT 返回字段内的相关标量子查询,可提升为Right Join;
    原理:不匹配场景需要补 NULL,使用右外连接,消除SubPlan+Broadcast。

不属于上面场景,均无法自动提升,执行计划生成SubPlan + Broadcast,属于性能隐患:

  1. TargetList 中带 count ()的相关子查询,内核无法自动提升;
  2. 非等值相关条件(>、<)SubLink:内核原生不支持自动提升
    • 人工改写思路:两次 JOIN(CorrelationKey + rownum 自关联)
    • 局限:GaussDB 无全局唯一 rowid;可尝试xc_nodeid + ctid模拟 rowid,但xc_nodeid重复率高,join 效率差;xc_node_id+ctid不能作为 hash join 关联键;业务层优先规避这类 SQL。
    • 改写须知:count 场景用CASE WHEN补 0;非 count 场景补 NULL;CTE 改写方案,支持 sharescan 时性能更优。
  3. 子查询内部包含两层表 JOIN;
  4. 子查询包含 with 子句、group by、having、集合操作、窗口函数、limit;
  5. 子查询包含易变函数;
  6. 多层嵌套复杂相关子查询。

四、优化实操示例

示例 1:表属性 + 索引优化

内层子查询表改为replication复制表,在关联过滤字段建立索引,减少 DN 间广播开销。

create table master_table (a int);
create table sub_table(a int, b int);
select a from master_table group by a having a in (select a from sub_table);

示例 2:SQL 改写消除 SubPlan

原始 SQL 存在SubPlan + Broadcast,性能差;改写为 exists,计划变为Hash Semi Join,消除 SubPlan。

-- 改写前
select * from master_table as t1 where t1.a in (select t2.a from sub_table as t2 where t1.a = t2.b);

-- 改写后
select * from master_table as t1 where exists (select t2.a from sub_table as t2 where t1.a = t2.b and t1.a = t2.a);

五、执行计划识别(排查手段)

表格

计划特征 含义 结论
SubPlan + Streaming(BROADCAST) 子查询未提升,外层每行循环执行子查询,内表广播到所有 DN ⚠️ 性能风险,需要优化
Hash Semi Join / Nested Loop Semi Join / Hash Right Join 子查询成功提升为 Join,无 SubPlan ✅ 优化完成

六、核心调优结论

  1. 子查询调优核心目标:尽可能触发 Sublink 提升,消灭 SubPlan 和 Broadcast;分布式 GaussDB 中 Broadcast 是主要性能杀手。
  2. IN、EXISTS 等值相关子查询更容易被内核提升,优先推荐EXISTS写法。
  3. 带 count、非等值、多表嵌套 join 的相关子查询,内核提升能力弱,必须人工改写或者业务规避。
  4. TargetList 标量子查询:不带 count 可提升;带 count 无法自动提升,需要 case when 改写处理空值。
posted @ 2026-09-27 19:58  打印helloworld  阅读(2)  评论(0)    收藏  举报