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

子查询上拉优化

最近更新时间:2026-07-21 09:53:01

我的收藏

功能概述

原生 PostgreSQL 的 pull_up_sublinks 只能上拉 ANY / EXISTS / NOT EXISTS 等有限形式的子查询;对于以下常见形态:
WHERE x = (SELECT agg(...) FROM ... WHERE ... = outer.col)(相关 EXPR 子查询)。
WHERE (SELECT ...) IN (...) 出现在 OR 分支中。
WHERE outer.col NOT IN (SELECT ...)(NOT IN → 反连接)。
SELECT (SELECT agg(...) FROM ... WHERE ... = outer.col), ... 目标列的相关聚合子查询。
主干只能生成 SubPlan/InitPlan,无法转成显式 JOIN,导致大表反复回扫。云数据库 PostgreSQL 提供 tencentdb_sublink 扩展,注册 pull_up_sublinks_hook,在保留主干兼容性的前提下把这些子查询实现路径转写为 JOIN / ANTI JOIN / UNION,让后续路径生成能真正利用 HashJoin、MergeJoin、并行等能力。
覆盖的转换矩阵:
TargetList SubLink 上拉:SELECT 列表内的相关聚合子查询作为 EXPR 上拉的一部分被处理。
OR clause SubLink 上拉:OR 分支内的可上拉 SubLink 改写为 LEFT JOIN + IS NOT NULL / equality。
EXPR SubLink 上拉:相关等值 EXPR 子查询上拉为 INNER/LEFT JOIN(当前 Phase 5 仅覆盖 hasAggs 子路径)。
NOT IN → ANTI JOIN:满足 NULL 安全条件时改写为反连接。
OR-子查询 → UNION 重写(可选):多路 OR-of-subqueries 合成一个 UNION 子查询后 JOIN。

前提条件

PostgreSQL 18,版本 ≥ v18.4_r1.10。

使用限制

CREATE EXTENSION tencentdb_sublink 只登记 catalog,不会加载动态库;必须配合 shared_preload_libraries = 'tencentdb_sublink' 或 LOAD 'tencentdb_sublink' 使用。
该扩展只调整 planner 阶段的重写,不改变查询结果集。

创建插件

CREATE EXTENSION tencentdb_sublink;

SELECT extname, extversion FROM pg_extension WHERE extname = 'tencentdb_sublink';
需要动态库实际加载。推荐加入 postgresql.conf:
shared_preload_libraries = '...,tencentdb_sublink'
或每个会话临时加载:
LOAD 'tencentdb_sublink';

参数说明

参数
类型
默认
说明
tencentdb_sublink.enable
bool
on
插件总开关。关闭则完全走原生 standard_pull_up_sublinks。
tencentdb_sublink.enable_or
bool
on
OR 子句中的 SubLink 上拉。
tencentdb_sublink.enable_not_in
bool
on
NOT IN (subquery) → ANTI JOIN。
tencentdb_sublink.enable_expr
bool
off
相关 EXPR SubLink 上拉(含聚合子路径)。
tencentdb_sublink.enable_targetlist
bool
off
目标列相关聚合子查询上拉。必须先打开 enable_expr 才生效,因为它作为 EXPR 上拉路径的辅助 helper 被调用。
tencentdb_sublink.enable_or_union
bool
off
OR-of-subqueries 合成 UNION 子查询后再连接。

使用示例

准备演示表:
CREATE TABLE t1 (a int, b int, c text);
CREATE TABLE t2 (a int, b int, c text);
CREATE TABLE t3 (a int, b int, c text);
INSERT INTO t1 SELECT i, i % 10, 'r'||i FROM generate_series(1,100) i;
INSERT INTO t2 SELECT i, i % 10, 'r'||i FROM generate_series(1,50) i;
INSERT INTO t3 SELECT i, i % 10, 'r'||i FROM generate_series(1,30) i;
ANALYZE t1, t2, t3;

默认已生效

SHOW tencentdb_sublink.enable;

NOT IN → ANTI JOIN

说明:
前提:外侧列与子查询目标列在 catalog 里都必须是 NOT NULL。
先给 t1.a / t2.a 加上 NOT NULL 约束:
ALTER TABLE t1 ALTER COLUMN a SET NOT NULL;
ALTER TABLE t2 ALTER COLUMN a SET NOT NULL;
SET tencentdb_sublink.enable = on;
SET tencentdb_sublink.enable_not_in = on;

postgres=> EXPLAIN (COSTS OFF)
postgres-> SELECT * FROM t1 WHERE t1.a NOT IN (SELECT t2.a FROM t2);
QUERY PLAN
----------------------------
Hash Anti Join
Hash Cond: (t1.a = t2.a)
-> Seq Scan on t1
-> Hash
-> Seq Scan on t2
(5 rows)
原本主干为 hashed SubPlan,插件将其重写为 Anti Join / Hash Anti Join,可与 HashJoin 结合。

OR 子句中的子查询上拉

SET tencentdb_sublink.enable to on;
SET tencentdb_sublink.enable_or = on;

postgres=> EXPLAIN (COSTS OFF)
postgres-> SELECT * FROM t1
postgres-> WHERE t1.b = 3
postgres-> OR EXISTS (SELECT 1 FROM t2 WHERE t2.a = t1.a);
QUERY PLAN
----------------------------------------------
Hash Left Join
Hash Cond: (t1.a = t2.a)
Filter: ((t1.b = 3) OR (t2.a IS NOT NULL))
-> Seq Scan on t1
-> Hash
-> HashAggregate
Group Key: t2.a
-> Seq Scan on t2
(8 rows)
OR 分支中的 EXISTS 被改写成 LEFT JOIN + ... IS NOT NULL 分支,避免整表回扫。

EXPR 相关子查询(含 TargetList 聚合子查询)

打开 enable_expr 后,WHERE 中的相关等值 EXPR 子查询可上拉:
SET tencentdb_sublink.enable_expr = on;

postgres=> EXPLAIN (COSTS OFF)
postgres-> SELECT * FROM t1
postgres-> WHERE t1.b = (SELECT sum(t2.b) FROM t2 WHERE t2.a = t1.a);
QUERY PLAN
-------------------------------------------------------
Hash Join
Hash Cond: ((t1.b = (sum(t2.b))) AND (t1.a = t2.a))
-> Seq Scan on t1
-> Hash
-> HashAggregate
Group Key: t2.a
-> Seq Scan on t2
(7 rows)
同时打开 enable_targetlist 时,SELECT 列表内的相关聚合子查询也进入同一路径:
SET tencentdb_sublink.enable_expr = on;
SET tencentdb_sublink.enable_targetlist = on;

postgres=> EXPLAIN (COSTS OFF)
postgres-> SELECT t1.a,
postgres-> (SELECT sum(t2.b) FROM t2 WHERE t2.a = t1.a) AS s
postgres-> FROM t1;
QUERY PLAN
----------------------------------
Hash Left Join
Hash Cond: (t1.a = t2.a)
-> Seq Scan on t1
-> Hash
-> HashAggregate
Group Key: t2.a
-> Seq Scan on t2
(7 rows)
说明:
EXPR / TargetList 目前仅覆盖 hasAggs 子路径。

OR-子查询 → UNION 重写

SET tencentdb_sublink.enable_or_union = on;

postgres=> EXPLAIN (COSTS OFF)
postgres-> SELECT * FROM t1
postgres-> WHERE t1.a IN (SELECT t2.a FROM t2 WHERE t2.b = 1)
postgres-> OR t1.a IN (SELECT t3.a FROM t3 WHERE t3.b = 2);
QUERY PLAN
-------------------------------------------
Hash Join
Hash Cond: (t1.a = t2.a)
-> Seq Scan on t1
-> Hash
-> HashAggregate
Group Key: t2.a
-> Append
-> Seq Scan on t2
Filter: (b = 1)
-> Seq Scan on t3
Filter: (b = 2)
(11 rows)
多路 OR-of-subqueries 会先合并成一个 UNION 子查询,再作为单一 SubqueryScan 与外层连接,避免每分支独立 SubPlan。