Postgresql源码(142)子查询提升pull_up_sublinks

news2025/3/31 19:40:52

1 案例

drop table t_fun01;
create table t_fun01 (image_id numeric primary key, content_id varchar(50), file_code varchar(20));
create index idx3 on t_fun01(content_id);
create index idx4 on t_fun01(file_code);
insert into t_fun01 select t.i, t.i%10, t.i%100 from generate_series(1,1000000) t(i);
analyze t_fun01;
explain analyze update t_fun01 t set content_id = 'null' where t.image_id in (select max(image_id) from t_fun01 where file_code = '1');

从计划上看,nestloop挂子查询Index Scan Backward确实是能想到的最优的计划:

postgres=# explain analyze update t_fun01 t set content_id = 'null' where t.image_id in (select max(image_id) from t_fun01 where file_code = '1');
                                                                              QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Update on t_fun01 t  (cost=4.69..12.73 rows=0 width=0) (actual time=0.257..0.259 rows=0 loops=1)
   ->  Nested Loop  (cost=4.69..12.73 rows=1 width=180) (actual time=0.135..0.138 rows=1 loops=1)
         ->  Subquery Scan on "ANY_subquery"  (cost=4.27..4.29 rows=1 width=88) (actual time=0.117..0.119 rows=1 loops=1)
               ->  Result  (cost=4.27..4.28 rows=1 width=32) (actual time=0.112..0.113 rows=1 loops=1)
                     InitPlan 1 (returns $0)
                       ->  Limit  (cost=0.42..4.27 rows=1 width=6) (actual time=0.107..0.108 rows=1 loops=1)
                             ->  Index Scan Backward using t_fun01_pkey on t_fun01  (cost=0.42..36389.43 rows=9467 width=6) (actual time=0.106..0.106 rows=1 loops=1)
                                   Index Cond: (image_id IS NOT NULL)
                                   Filter: ((file_code)::text = '1'::text)
                                   Rows Removed by Filter: 99
         ->  Index Scan using t_fun01_pkey on t_fun01 t  (cost=0.42..8.44 rows=1 width=12) (actual time=0.014..0.014 rows=1 loops=1)
               Index Cond: (image_id = "ANY_subquery".max)
 Planning Time: 0.532 ms
 Execution Time: 0.345 ms
(14 rows

优化器是如何上拉子查询的呢?

2 上拉子查询流程分析

2.1 pull_up_sublinks

  • pull_up_sublinks 是优化器中用于子链接(SubLink)上拉优化的核心函数。
  • 通过递归处理查询树中的子链接(如 ANYEXISTS),将其转换为**半连接(Semi Join)反半连接(Anti Join)**结构,从而提升查询性能。

在当前案例中,pull_up_sublinks函数拿到的Query树如下:
在这里插入图片描述
pull_up_sublinks递归入口

  • 调用 pull_up_sublinks_jointree_recurse 遍历 jointree,处理 RangeTblRef(单表)、FromExpr(FROM列表)和 JoinExpr(JOIN操作)中的子链接。
  • 调用 pull_up_sublinks_qual_recurse 处理条件表达式中的子链接,如 WHERE col = ANY (SELECT ...)

子链接转换逻辑

  • ANY 子链接:通过 convert_ANY_sublink_to_join 转换为 Semi Join,生成新的连接条件(如 col = t2.key
  • EXISTS 子链接:通过 convert_EXISTS_sublink_to_join 转换为 Anti Join,优化为外连接形式

2.2 convert_ANY_sublink_to_join

(gdb) bt
#0  convert_ANY_sublink_to_join (root=0x2b64f60, sublink=0x2a6f848, available_rels=0x2b653c0) at subselect.c:1272
#1  0x00000000008a1606 in pull_up_sublinks_qual_recurse (root=0x2b64f60, node=0x2a6f848, jtlink1=0x7ffd2e7e8318, available_rels1=0x2b653c0, jtlink2=0x0, available_rels2=0x0)
    at prepjointree.c:508
#2  0x00000000008a12e1 in pull_up_sublinks_jointree_recurse (root=0x2b64f60, jtnode=0x2b64cb0, relids=0x7ffd2e7e8380) at prepjointree.c:379
#3  0x00000000008a10e4 in pull_up_sublinks (root=0x2b64f60) at prepjointree.c:315
#4  0x0000000000886ad1 in subquery_planner (glob=0x2a6f968, parse=0x2a6fa78, parent_root=0x0, hasRecursion=false, tuple_fraction=0) at planner.c:689
#5  0x0000000000885e98 in standard_planner (parse=0x2a6fa78,
    query_string=0x2a6e538 "update t_fun01 t set content_id = 'null' where t.image_id in (select max(image_id) from t_fun01 where file_code = '1');", cursorOptions=2048,
    boundParams=0x0) at planner.c:413

进入convert_ANY_sublink_to_join时available_rels=[1,0]指向t_image,拿到的sublink:
在这里插入图片描述
convert_ANY_sublink_to_join

  • 输入:调用者已在查询的某个条件子句(qual clause)顶层发现了一个 ANY 子链接,但尚未进一步检查该子链接的属性。
  • 功能:判断是否适合以连接方式处理此子链接。若适合,构造并返回一个 JoinExpr 结构;否则返回 NULL(表示无法转换)。
  • 关键限制参数:available_rels
    • 含义:允许在子链接表达式中安全引用的父查询关系集合(即表或视图的标识集合)。
    • 作用:避免当子链接出现在外连接(Outer Join)的 ON 条件中时,因引用非法关系而改变查询语义。
    • 强制要求:若转换后的条件引用了 available_rels 之外的父查询关系,则转换必须失败。
JoinExpr *
convert_ANY_sublink_to_join(PlannerInfo *root, SubLink *sublink,
							Relids available_rels)

第一步判断:pull_varnos拿到 子查询里面 引用了那些level=0的基表,这里upper_varnos只有一个元素1。因为只用了一个RTE:t_fun01

	upper_varnos = pull_varnos(root, sublink->testexpr);
	if (bms_is_empty(upper_varnos))
		return NULL;

第二步判断:不能引用available_rels之外的表。

	if (!bms_is_subset(upper_varnos, available_rels))
		return NULL;
							

第三步判断:不能带v类型的函数。

	if (contain_volatile_functions(sublink->testexpr))
		return NULL;

到这里就说明可以转,开始拼join,增加一个RTE叫做ANY_subquery,所以可以在计划里面看到这个名字。

	pstate = make_parsestate(NULL);
	nsitem = addRangeTableEntryForSubquery(pstate,
										   subselect,
										   makeAlias("ANY_subquery", NIL),
										   false,
										   false);
	rte = nsitem->p_rte;
	parse->rtable = lappend(parse->rtable, rte);
	rtindex = list_length(parse->rtable);
	rtr = makeNode(RangeTblRef);
	rtr->rtindex = rtindex;
	subquery_vars = generate_subquery_vars(root,
										   subselect->targetList,
										   rtindex);
	quals = convert_testexpr(root, sublink->testexpr, subquery_vars);
	result = makeNode(JoinExpr);
	result->jointype = JOIN_SEMI;
	result->isNatural = false;
	result->larg = NULL;		/* caller must fill this in */
	result->rarg = (Node *) rtr;
	result->usingClause = NIL;
	result->join_using_alias = NULL;
	result->quals = quals;
	result->alias = NULL;
	result->rtindex = 0;
	return result;

3 pull_varnos

功能

  • 工具函数,其核心作用是 从语法树中提取所有引用“零层级”基表RTE的变量编号varnos集合。
  • varno:变量编号,唯一标识查询中引用的基表(如 FROM 后的表、子查询、连接结果等)。
  • 零层级(level-zero):指当前查询层级的基表(不包含嵌套子查询中的表)。
  • nullingrels:外连接中可能导致列值为 NULL 的基表集合(如 LEFT JOIN 的右表)。

工作方式:

  • query_or_expression_tree_walker递归遍历语法树,调用 pull_varnos_walker 处理每个节点
  • Var 节点:提取其 varno 和 varnullingrels(外连接空值关系)。
  • PlaceHolderVar 节点:若 root 非空,提取其 phnullingrels。
  • SubLink 节点:递归处理子链接内的表达式,捕捉上层的 varno 引用。
  • SubPlan 节点:仅处理传递给子计划的参数(子计划内部已独立优化)。

层级控制

  • sublevels_up 计数器:跟踪嵌套子查询的层级。
  • 当进入子查询时,计数器递增,仅收集当前层级(零层级)的 varno。
  • 嵌套子查询中的 varno 会被忽略(因其属于深层级)。
Relids
pull_varnos(PlannerInfo *root, Node *node)
{
	pull_varnos_context context;

	context.varnos = NULL;
	context.root = root;
	context.sublevels_up = 0;

	/*
	 * Must be prepared to start with a Query or a bare expression tree; if
	 * it's a Query, we don't want to increment sublevels_up.
	 */
	query_or_expression_tree_walker(node,
									pull_varnos_walker,
									(void *) &context,
									0);

	return context.varnos;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2323619.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

23种设计模式-桥接(Bridge)设计模式

桥接设计模式 🚩什么是桥接设计模式?🚩桥接设计模式的特点🚩桥接设计模式的结构🚩桥接设计模式的优缺点🚩桥接设计模式的Java实现🚩代码总结🚩总结 🚩什么是桥接设计模式…

【黑皮书】 AVL树

目录 前言 一 AVL树的介绍 二 单旋转 二 双旋转 总结 前言 AVL树的学习 一 AVL树的介绍 AVL树是带有平衡条件的二叉查找树,这个平衡条件要持续保持,而且必须保证树的深度为O(log(N))最简单的想法就是要求左右子树具有相同的高度 一棵AVL树是…

【机器学习】什么是决策树?

什么是决策树? 决策树是一种用于分类和回归问题的模型。它通过一系列的“决策”将数据逐步分裂,最终得出预测结果。可以把它看作是一个“树”,每个节点表示一个特征的判断,而每个分支代表了可能的判断结果,最终的叶子…

使用独立服务器的最佳方式指南

在寻找合适的主机服务方案时,可以考虑独立服务器,因为它拥有管理员权限以及更高的性能配置。在本指南中,我们将介绍独立服务器的多种用途,并分析为什么选择独立服务器可能是处理高性能、资源密集型应用和大流量网站的最佳方案。 搭…

【HTML 基础教程】HTML 属性

HTML 属性 属性是 HTML 元素提供的附加信息。 属性通常出现在 HTML 标签的开始标签中,用于定义元素的行为、样式、内容或其他特性。 属性总是以 name"value" 的形式写在标签内,name 是属性的名称,value 是属性的值。 HTML 属性 …

爬虫问题整理(2025.3.27)

此时此刻,困扰我一天的两个问题终于得到了解决,在此分享给大家。 问题1:使用anaconda prompt无法进行pip安装,这里只是一个示例,实际安装任何模块都会出现类似报错。 解决办法:关掉梯子......没错&#xf…

短信验证码安全需求设计

背景: 近期发现部分系统再短信充值频繁,发现存在恶意消耗短信额度现象,数据库表排查,发现大量非合法用户非法调用短信接口API导致额度耗尽。由于系统当初设计存在安全缺陷,故被不法分子进行利用,造成损失。…

若依专题——基础应用篇

若依搭建 搭建后端项目 ① Git 克隆并初始化项目 ② MySQL 导入与配置 ③ 启动 Redis 搭建后端项目注意事项? ① 项目初始化慢,执行clean、package ② MySQL导入后,修改application-druid.yml ③ Redis有密码,修改ap…

AI for CFD入门指南(传承版)

AI for CFD入门指南 前言适用对象核心目标基础准备传承机制 AI for CFDLibtorch的介绍与使用方法PytorchAutogluon MakefileVscodeOpenFOAMParaviewGambit 前言 适用对象 新加入课题组的硕士/博士研究生对AICFD交叉领域感兴趣的本科生实习生需要快速上手组内研究工具的合作研…

DeepSeek+RAG局域网部署

已经有很多平台集成RAG模式,dify,cherrystudio等,这里通过AI辅助,用DS的API实现一个简单的RAG部署。框架主要技术栈是Chroma,langchain,streamlit,答案流式输出,并且对答案加上索引。支持doc,docx,pdf,txt。…

个人学习编程(3-24) 数据结构

括号的匹配&#xff1a; if((s[i]) && now() || (s[i]] && now[)){ #include <bits/stdc.h>using namespace std;int main() {char s[300];scanf("%s",&s);int i;int len strlen(s);stack <char> st;for (i 0; i < len; i){if(…

面试八股文--框架篇(SSM)

一、Spring框架 1、什么是spring Spring框架是一个开源的Java平台应用程序框架&#xff0c;由Rod Johnson于2003年首次发布。它提供了一种全面的编程和配置模型&#xff0c;用于构建现代化的基于Java的企业应用程序。Spring框架的核心特性包括依赖注入&#xff08;DI&#xf…

跨语言语言模型预训练

摘要 最近的研究表明&#xff0c;生成式预训练在英语自然语言理解任务中表现出较高的效率。在本研究中&#xff0c;我们将这一方法扩展到多种语言&#xff0c;并展示跨语言预训练的有效性。我们提出了两种学习跨语言语言模型&#xff08;XLM&#xff09;的方法&#xff1a;一种…

Nodejs上传文件的问题

操作系统&#xff1a;window和linux都会遇到 软件环境&#xff1a;v20.10.0的Nodejs 1、前端代码如下&#xff1a; 2、后端Nodejs 2.1、注册接口 2.2、上传接口 其中memoryUpload方法代码如下&#xff1a; 3、用页面上传文件 查看具体报错原因&#xff1a; TypeError: sourc…

无人机螺旋桨平衡标准

螺旋桨平衡是确保无人机(UAV)平稳运行、可靠性和使用寿命的关键过程。螺旋桨的不平衡会导致振动、噪音&#xff0c;并加速关键部件的磨损&#xff0c;从而对飞行性能产生负面影响。 ISO 21940-11:2016标准为旋翼平衡提供了一个广泛引用的框架&#xff0c;定义了可接受的不平衡…

SpringBoot+策略模式+枚举类,使用配置文件改进,优雅消除if-else,完全符合OOP原则

需求分析 公司做物联网系统的&#xff0c;使用nettry进行设备连接&#xff0c;对设备进行数据采集&#xff0c;根据设备的协议对数据进行解析&#xff0c;解析完成之后存放数据库&#xff0c;但是不同厂家的设备协议不同。公司系统使用了使用了函数式编程的去写了一个解析类&am…

每日免费分享之精品wordpress主题系列~DAY16

主题介绍&#xff1a; 今日在网上寻找wordpress主题的时候逛到了大叔的网站&#xff0c;赶脚这个主题蛮不错的&#xff0c;于是百度一下&#xff0c;果然&#xff0c;这个主题很受欢迎。作为主题下载站追梦者也不甘落后&#xff0c;马上就发布出来了&#xff0c;希望对你们有用…

OpenCV图像拼接(9)实现图像拼接功能的一个高级接口cv::Stitcher

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::Stitcher 类是OpenCV中用于实现图像拼接功能的一个高级接口。它简化了从一组部分重叠的图像创建全景图的过程&#xff0c;隐藏了许多底层细节…

dify + deepseek /qwen + win +xinference 等完成知识库建设

Dify.AI The Innovation Engine for Generative AI Applications 简介&#xff1a;Dify是一个用于构建人工智能应用程序的开源平台。我们将后端即服务和LLMOps相结合&#xff0c;简化了生成式人工智能解决方案的开发&#xff0c;使开发人员和非技术创新者都可以使用。CPU>…

模数转换电路(A/D转换器)

A/D转换&#xff0c;是将输入的模拟电压量转换成相应的数字量。 A/D转换器的类型很多&#xff0c;按工作原理可分为直接转换型和间接转换型两大类。前者直接将模拟电压量转换成数字量&#xff0c;后者是先将模拟电压量转换成一个中间量&#xff0c;再将中间量转换成数字量。 …