功能概述
原生 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 JoinHash 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 t1postgres-> WHERE t1.b = 3postgres-> OR EXISTS (SELECT 1 FROM t2 WHERE t2.a = t1.a);QUERY PLAN----------------------------------------------Hash Left JoinHash Cond: (t1.a = t2.a)Filter: ((t1.b = 3) OR (t2.a IS NOT NULL))-> Seq Scan on t1-> Hash-> HashAggregateGroup 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 t1postgres-> WHERE t1.b = (SELECT sum(t2.b) FROM t2 WHERE t2.a = t1.a);QUERY PLAN-------------------------------------------------------Hash JoinHash Cond: ((t1.b = (sum(t2.b))) AND (t1.a = t2.a))-> Seq Scan on t1-> Hash-> HashAggregateGroup 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 spostgres-> FROM t1;QUERY PLAN----------------------------------Hash Left JoinHash Cond: (t1.a = t2.a)-> Seq Scan on t1-> Hash-> HashAggregateGroup 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 t1postgres-> 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 JoinHash Cond: (t1.a = t2.a)-> Seq Scan on t1-> Hash-> HashAggregateGroup Key: t2.a-> Append-> Seq Scan on t2Filter: (b = 1)-> Seq Scan on t3Filter: (b = 2)(11 rows)
多路 OR-of-subqueries 会先合并成一个 UNION 子查询,再作为单一 SubqueryScan 与外层连接,避免每分支独立 SubPlan。