news 2026/7/30 12:56:13

告别低效查询:腾讯云 PostgreSQL Push Pred 助力性能飞跃

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
告别低效查询:腾讯云 PostgreSQL Push Pred 助力性能飞跃

告别低效查询:腾讯云 PostgreSQL Push Pred 助力性能飞跃

谓词下推是数据库优化器老生常谈的优化特性,为什么需要谓词下推?其实核心就一个——让数据过滤“越早越好”,减少后续计算的压力。

每个数据库都有自己谓词下推的算法,这里面最特殊的,我觉得应该算是 PostgreSQL 的谓词下推,因为我们经常在其他数据库的代码里看到谓词下推的动作,但是 PostgreSQL 的谓词下推是没有具体下推动作的,他是基于 join type/join 条件类型/等价传递闭包等能力先进行谓词拆解,再把谓词组装到“正确”的位置,实现下推。

下面我们从功能的角度来看一下 PostgreSQL 这个三十多年的老数据库的谓词下推的发展历史,最后也看一下在腾讯云增加的 push pred 能力的实现方法。

常量谓词下推

1999年,当时有位叫 Bernard Frankpitt 的开发者,发现了一个奇怪的问题:同样是查询一张带索引的表,用常量过滤条件(Filter)能走索引,可把常量换成“常量函数”,优化器就“瞎了”,直接走全表扫描。

举个例子,表t1有个int4类型的字段a1,建了索引t1_idx。执行:

SELECT*FROMt1WHEREt1.a1=5;

优化器能精准用上索引;

但执行:

SELECT*FROMt1WHEREt1.a1=sqr(2);

优化器却选择了全表扫描(其中 sqr 是求平方的函数,sqr(2)本质就是4)。

原因很简单:当时 PostgreSQL 的优化器只能识别“索引列 op 常量”的格式,却没意识到“常量作为参数的函数”,本质也是常量。这就导致明明可以走索引的查询,白白浪费了性能。

为了解决这个问题,Bernard 提交了一个补丁,在优化器里加了个叫 eval_const_expr_mutator 的递归函数,专门识别并计算“只有函数、运算符、常量组成的子表达式”,把它们提前转换成常量。这个补丁最终落地,也为后来的谓词下推打下了第一个基础——让优化器能“看透”更多隐藏的常量约束。

块内谓词下推

解决了常量函数的问题后,优化器开始朝着“更智能的约束分发”进化,这一阶段的核心是「restriction predicate(限制谓词)」的块内传播——也就是在同一个查询块(Query Block)里,把单表约束、常量约束尽量往前推,推到最底层的基础表上,尽早过滤数据。

2003年1月,Tom Lane 提交的 de97072e3c8:允许 merge 和 hash join 支持任意表达式(只要不含 volatile 易变函数),而不是只能基于“Var=Var”的等式。这意味着,优化器能识别更复杂的约束,比如“a.x = b.y and b.y = 42”,能自动推导出“a.x=42”,并把这个约束下推到表 a 上。

紧接着,还是 Tom Lane,在2003年1月24日提交了 f5e83662d06:修改了优化器的隐含等式推导逻辑。当一组等式里包含常量(或外层查询的参数)时,会主动抑制冗余的“var=var”约束,只保留“var=常量”的约束。这样做既能减少执行时的计算量,也能让约束更精准地推到基础表。

这些修改再累加上1999年的常量函数优化,慢慢形成了 PostgreSQL 的块内限制谓词(常量谓词,Filter)下推能力——它是由常量折叠、约束归类、等价传递闭包等多个小优化,共同拼凑起来的能力。

视图/子查询里的谓词“推不下去”

事情往往是余波未平,一波又起。老的问题刚刚解决,新的痛点又出现了:当查询里有视图(本质是子查询)或者 UNION/INTERSECT 子查询时,外层的 WHERE 条件没法下推到内层子查询里,导致内层子查询要返回所有数据,再由外层过滤,性能极差。

比如有人写了这样的查询:

SELECT*FROM(SELECTaFROMt1UNIONSELECTaFROMt2)WHEREa>10;

按道理,应该把“a>10”这个条件分别下推到 t1 和 t2 的查询里,减少 UNION 的数据量,但当时 PostgreSQL 做不到。

2002年8月,PostgreSQL 社区展开了讨论,核心是“把外层谓词下推到 UNION/INTERSECT 子查询里,到底合法吗?”。Curt Sampson 提出,视图作为“查询宏”,优化器应该能像优化原生查询一样,把外层条件下推;而 Tom Lane 则谨慎地分析了不同场景的合法性——毕竟 SQL 的三值逻辑(true/false/null)很容易踩坑。

最终,Tom Lane 给出了结论:UNION、INTERSECT 场景下,谓词下推是合法的(虽然会改变行为,但仍符合 SQL 标准);但 EXCEPT 场景不行,因为下推可能导致结果不符合预期。基于这个结论,他提交了 0201dac1c31 这个关键 commit:实现了“将外层约束下推到 UNION 和 INTERSECT 子查询”,这是 PostgreSQL 第一次实现跨查询块的谓词下推,也是 pushpred 的合法性的重要铺垫。

Parameterized Path (参数化路径)

数据库的优化永无止境,解决了常量谓词的下推之后,人们又把目光挪向了 join predicate。

2002年10月,Hans-Jürgen Schönig 发现一个查询里,重复写了一次 join 条件(既在 JOIN ON 里写了,又在 WHERE 里写了),反而比只写一次更快。原因就是,重复的 join 条件被优化器当作 restriction 谓词,下推到了内层扫描,减少了数据量。

Tom Lane 针对这个问题,后续通过 04c8785c7b2 这个 commit,重构了嵌套循环内层索引扫描的规划逻辑——让 join 条件能更顺畅地分发到内层路径,避免了“重复写条件才能提速”的尴尬。再加上之前 de97072e3c8 支持复杂表达式的 join,PostgreSQL 在块内的 join 谓词优化已经很成熟了,但它的边界很明确:只能在同一个查询块里生效。

这种实现的本质是让内层子路径由外层参数驱动执行——简单说,就是外层每返回一行数据,就把对应的参数传给内层,内层用这个参数过滤数据,再返回结果。

看上去是一个非常常规的优化,但是他让 Path 之间产生了依赖关系,外层是驱动层,内层是被驱动层,只有 Nested loop Join 算子能够实现这种驱动和被驱动的关系,所以在 Join 类型的选择上会受到一些限制。同时在 join ordering 的搜索、Join 合法性判断上,需要考虑更多的特殊情况。

LATERAL 成为基础设施

2012年8月,Tom Lane 提交了 5ebaaa49445 这个里程碑式的 commit:实现了 SQL 标准的 LATERAL 子查询。LATERAL 的作用很简单——允许 FROM 子句里的子查询,引用外层查询的列,让跨块相关性从“隐含技巧”变成了“标准语法”。

有了 LATERAL,优化器就有了合法的语义入口,可以名正言顺地处理“外层变量驱动内层子查询”的场景;而 Parameterized Path 提供了执行层的支持,块内 join 谓词分发提供了优化思路——到这里,pushpred 的所有铺垫都已到位。

举个例子:假设存在两张表,用户表 users(id int, name varchar)和订单表 orders(id int, user_id int, amount numeric),查询“每个用户的订单总金额”,未支持 LATERAL 时,需用子查询关联,无法直接在子查询中引用外层 users.id;支持 LATERAL 后,可直接写:

SELECTu.name,o.total_amountFROMusers uLEFTJOINLATERAL(SELECTSUM(amount)AStotal_amountFROMordersWHEREuser_id=u.id)oONtrue;

这里子查询中的 user_id = u.id,就是直接引用外层 users 表的 id 字段,实现了跨块相关性的合法引用,也为后续 pushpred 下推 join 谓词提供了语义基础。

push pred 登场

腾讯云 PostgreSQL 实现了 Push Down Join Clause to Subquery 优化,新增 GUC tencentdb_enable_push_pred,它解决了最后一个核心问题:把外层查询块的 join 谓词,迁移到内层子查询块里,让内层子查询在规划路径时,就能用上 join 约束,从而选择更优的执行计划。

在 Push pred 登场之前,DBA 如果想优化一个 SQL 的性能,是可以通过改写 SQL 的方法,用 Lateral 显式的来提升性能的,而 pushpred,就是通过优化器自动的实现这个谓词的下推。

咱们用一个例子理解:有一个查询,外层是表 a,内层是一个 LATERAL 子查询(依赖a的列),join 条件是 a.id = 子查询.b.id。在 pushpred 出现之前,join 条件只能挂在 join 节点上,内层子查询规划时,不知道 a.id 的值,只能返回所有数据,再由 join 节点过滤;有了 pushpred 之后,join 条件“a.id = b.id”会被下推到内层子查询,内层可以用这个条件走索引扫描,大幅减少返回数据量。

总结

数据库性能的优化永无止境,无论是常量谓词、还是 Join 谓词、亦或是各种动态 Filter,本质上都是尽早的过滤数据,我们上面举得各种例子看上去简单明了,而优化器的难点在于,要保证这种优化在复杂查询(如果你经常见到那种几百行一句的 SQL)里仍然是正确的,每个优化都需要做细致的论证才行。push pred 能力不是一个“全新发明”,而是站在前面所有优化的肩膀上进一步的优化,是 PostgreSQL 这个“老”数据库几十年沉淀积累出来的结果。

了解功能特性及使用方法请参见:
https://cloud.tencent.com/document/product/409/134538

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/30 12:54:37

BBWEYY 跨境电商低成本获客转化解决方案:DTC品牌用BBWEYY独立站提升复购与客户终身价值实战,含零代码SAAS、AI编程、源码定制交付

跨境电商实战指南 DTC品牌用BBWEYY独立站提升复购与客户终身价值实战 从一次成交走向会员、内容、订阅与长期客户关系 干货分享|美妆、服饰、家居、健康、宠物与消费电子DTC品牌 DTC品牌真正的利润,不只来自首单,而来自能够持续识别、触达…

作者头像 李华
网站建设 2026/7/30 12:54:01

如何5分钟学会AI智能分层:Layerdivider图像分层工具完整指南

如何5分钟学会AI智能分层:Layerdivider图像分层工具完整指南 【免费下载链接】layerdivider A tool to divide a single illustration into a layered structure. 项目地址: https://gitcode.com/gh_mirrors/la/layerdivider 你是否曾经面对一张精美的插画或…

作者头像 李华
网站建设 2026/7/30 12:51:00

音频处理技术实战:从语音合成到批量处理的完整方案

这次我们来看一个特殊的音频处理项目——台配陈美贞版《第1145集 招鬼香》的本地化处理方案。这个项目主要涉及语音合成、音频编辑和批量处理能力,适合需要处理特定配音版本音频内容的创作者。 从技术角度看,这类项目最值得关注的是语音处理的精准度和批…

作者头像 李华
网站建设 2026/7/30 12:46:42

非侵入式负荷监测(NILM)系统设计:从电赛项目到智能用电实践

1. 项目概述与核心价值 看到“单相用电器分析监测装置”这个题目,尤其是后面跟着“2017年全国大学生电子设计竞赛试题”这个标签,相信很多电子、自动化相关专业的同学和工程师都会会心一笑。这绝对是一个经典中的经典,一个能把你从理论课本直…

作者头像 李华
网站建设 2026/7/30 12:44:21

C语言分支与循环结构详解与性能优化

1. C语言分支与循环基础解析作为一门经典的编程语言,C语言的分支和循环结构是构建程序逻辑的基础骨架。在实际开发中,约70%的代码都会涉及这两种控制结构。不同于现代语言的各种语法糖,C语言用最简洁的语法实现了完整的流程控制能力。初学者常…

作者头像 李华
网站建设 2026/7/30 12:42:37

邮寄大件重货,除了京东、德邦外,这些物流更便宜!

很多人寄大件除了选京东就是德邦,京东、德邦确实在大件运输上比较专业,但是整体价格也相对较高,今天我们来讲讲寄大件物品,京东、德邦以及安能、百世、顺心捷达等物流公司应该怎么选。家电产品:优先京东家电自带保价、…

作者头像 李华