PostgreSQL 优化器代码概览

简介

PostgreSQL 的开发源自上世纪80年代,它最初是 Michael Stonebraker 等人在美国国防部支持下创建的POSTGRE项目。上世纪末,Andrew Yu 等人在它上面搭建了第一个SQL Parser,这个版本称为Postgre95,也是加州大学伯克利分校版本的PostgreSQL的基石[1]。

我们今天看到的 PostgreSQL 的优化器代码主要是 Tom Lane 在过去的20年间贡献的,令人惊讶的是这20年的改动都是持续一以贯之的,Tom Lane 本人也无愧于“开源软件十大杰出贡献者”的称号。

但是从今天的视角,PostgreSQL 优化器不是一个好的实现,它用C语言实现,所以扩展性不好;它不是 Volcano 优化模型的[2],所以灵活性不好;它的很多优化复杂度很高(例如Join重排是System R[3]风格的动态规划算法),所以性能不好。

无论如何,PostgreSQL 是优化器的优秀实现和创新源头(想象 Greenplum 和 ORCA 等一系列项目),它的一些优化手段和代码结构在今天仍然是值得借鉴的,包括:

  1. 参数化路径,作用于indexed lookup join
  2. 分区裁剪和并行优化
  3. 强一致的cardinality estimation保证

本文尝试快速地浏览一遍 PostgreSQL 优化器的代码,和现代优化器比较优缺点。大部分的 PostgreSQL 优化器代码来自于 https://github.com/postgres/postgres/tree/master/src/backend/optimizer。 我们提到现代优化器主要指的是 Apache Calcite 和 ORCA。

术语解释

  1. Datum
  2. Qual
  3. Path

关键数据结构

查询

  1. __Query__: Parse Tree,优化器的输入
  2. __RangeTblEntry__: Parse Tree的一个节点,它描述了一个数据集的视图,这个数据集可能来源于某个子查询、Join、Values或任何一个简单关系代数表达式。Join实现需要把它的输入都表达为 RangeTblEntry (以下简称RTE)。

执行计划

  1. __PlannedStmt__: 执行计划的顶层节点
  2. __PlannerInfo__: 优化器的上下文信息。它是一个树形结构,用parent_root变量指向父节点。一个Query包含一个或多个PlannerInfo,每次Join切分一次树节点。它包含RelOptInfo的指针。
  3. __RelOptInfo__: 优化器的核心数据结构,包含一个子查询的Path集合等信息。这个概念对应于ORCA的Group或Calcite中的Set。
  4. __Path__: 区别于Parser称Relational Expression为Node,Optimizer称优化时的关系代数为Path。Path是物理计划,它包含优化器对于单个关系代数的理解,包括并行度、PathKey和cost。
  5. __PathKey__: 排序属性。这个概念相当于Volcano中的Physical Property或Calcite中的Trait。因为 PostgreSQL 是单机数据库,仅用排序属性就可以表达所有算法的需求和实现特性。对于分布式数据库,通常还需要分布属性。

主流程

子查询上拉

因为优化的单元(RelOptInfo)是子查询,合并子查询可以简化优化流程。关联的过程包括:

  1. __pull_up_sublinks__: 将可转换的 ANY/EXISTS 子句转换为 (anti-)semi-join 。一些优化器称这个过程为de-correlation。
  2. __pull_up_subqueries__: 将可上拉的子查询上拉到当前查询,删除原来的子查询。如果子查询是一个 Join ,这个操作相当于打平 binary join 到 multi join。

EquivalenceClass解析

Equivalence Class(EC)是 qual 的术语,它指代的是 expression 的等价性。例如,expression

a = b AND b = c

则称 {a, b, c} 是一个EC。特别地,在 PostgreSQL 中,expression

a = b AND b = 5

只生成简化的EC:{a = 5} {b = 5}

EC是很关键的数据结构,它的应用场景包括:

  1. 在 Join 时,EC用来决定 Join Key,它决定了 Outer Join 简化和PathKey设定
  2. 在 Join 时决定 qual 穿越
  3. 决定参数化路径的参数列表
  4. 匹配主-外键约束,以便优化(Join的)cardinality estimation

EC是一个树形结构,每个节点是一个EC,并链接到它合并的父节点上。考虑a = b AND b = c的例子,最后的EC tree表达为

{a, b, c}
|- {a, b}
|- {b, c}       

其中,每个EC内部的expression称为EquivalenceMember(EM)。

生成 EC 的入口是 generate_base_implied_equalities ,它从 query_planner 调入。也就是说,EC是在规划Join的前一刻生成的,这个阶段解析EC的代价最小,但是也决定了EC只能应用于Join优化。

Join重排

(有关Join重排的背景知识可以参考我之前的文章 SQL优化器原理 - Join重排)

make_rel_from_joinlist是Join重排的入口,当前版本的 PostgreSQL 有三种算法:

  1. 你可以插入一个自定义的Join重排算法
  2. GEQO: Genetic Optimization (基因算法,或遗传算法[4]),是一种非穷举的最优化算法实现
  3. Standard:一个略微剪枝的动态规划算法。

默认在12路及以上的复杂Join中会打开GEQO。可以在postgresql.conf中修改参数

geqo = on
geqo_threshold = 12

控制GEQO设定。

现在让我们检查 Standard 算法。它的主入口在 join_search_one_level ,每次在已生成的局部计划的基础上:

  1. 按EC检查未加入的Join input,加入到生成的局部计划,这个操作仅产生 Left-deep-tree
  2. 从未加入局部计划的Join input里找到有EC的两个input,生成额外的局部计划,用于生成Bushy-tree
  3. 如果当前层找不到任何EC关联,生成笛卡尔积。

上述描述已经足够复杂,让我们总结一下 Standard 算法:

  1. Standard 算法仍然是一个穷举的动态规划算法
  2. 它对 a-b/b-a 镜像去重,同时当EC存在时不考虑笛卡尔积,这些工程上的降级有效降低了搜索复杂度

路径生成和动态规划

如上所述,优化过程集中在对子查询(RelOptInfo)的重建过程,这可以理解为逻辑优化过程,这通常是跨关系代数操作符的、比较复杂的优化。事实上 PostgreSQL 也同步在做物理优化。

物理优化就是将 Path 加入 RelOptInfo。考虑Join,物理优化的入口在 populate_joinrel_with_paths。对每个JoinRel(Join RelOptInfo),考虑:

  1. sort_inner_and_outer:两边排序的MergeJoin路径
  2. match_unsorted_outer:Null-generating side不排序路径,包括 MergeJoin 和 NestedLoopJoin 。
  3. hash_inner_and_outer:两边哈希的HashJoin路径。

有趣的点是HashJoin路径(hash_inner_and_outer),顾名思义,它要求Join两边都计算哈希值。在生成Path过程中,需要计算两边的参数信息。例如A join B on A.x = B.y,对于A来说,x是参数,对于B是y。如果选定A作为Probe side,一旦B上有y的索引,每次x的probe将以参数的形式传递给y的索引。通过调用 get_joinrel_parampathinfo 来产生参数信息。

路径生成的入口是add_path,每次生成路径,需要更新RelOptInfo的最佳路径和最小代价以便后续动态规划选择全局最优。

流程图

planner
|- subquery_planner 迭代的子查询优化|- pull_up_sublinks de-correlation|- pull_up_subqueries 子查询上拉|- preprocess_expression Query/PlannerInfo 结构解析,常量折叠|- remove_useless_groupby_columns|- reduce_outer_joins Outer Join退化|- grouping_planner|- plan_set_operations SetOp优化|- query_planner 子查询优化主入口|- generate_base_implied_equalities 生成/合并EC|- make_one_rel Join优化入口|- set_base_rel_pathlists 生成Join RelOptInfo列表|- make_rel_from_joinlist Join重排和规划|- standard_join_search 标准Join重排算法|- join_search_one_level|- make_join_rel 生成JoinRel和对应的Path|- create_XXX_paths Grouping、window等其他expression优化

讨论

扩展性和灵活性

首先,PostgreSQL 的优化器代码可以说非常复杂,这已经极大限制了它的扩展性和灵活性。如果看一眼这部分代码的更新日志,会发现里面的作者已经只有少数几个人。

一部分扩展性限制是由编程语言带来的,因为C语言本身不容易扩展,这意味着大部分时候想要添加一个新的Node或Path变得很不容易,你需要定义一系列的数据结构、Cardinality Estimation逻辑、并行逻辑和Path解释逻辑。并没有类似interface这样的抽象指导你该怎么做。虽然,PostgreSQL 的代码已经写得非常工整,而且也有很多的文章告诉你该怎么做(比如 Introduction to Hacking PostgreSQL 和 The Internals of PostgreSQL)。

另一部分扩展性限制是优化器本身的结构带来的。现代的优化器基本都是Volcano Model[2]的(例如SQL Server和Oracle,就像他们声称的那样),而 PostgreSQL 没有实现为 Volcano Model 这种 Generic purpose,pluggable 的形式。影响包括:

  1. 无法做逻辑和物理优化的互操作。例如前文说到的,一个Join产生的EC必须和它紧跟的 RTE 结合才能产生 IndexedLookupJoin,而不像其他优化器可以把这个 EC (它在某种意义上已经是物理计划)下推到合适的逻辑计划上,指导它做物理计划转换。
  2. 不容易定制优化规则。
  3. 开发者关注的切片太大,开发一个优化规则除了关注优化本身,不得不学习其他优化规则的数据结构、动态规划更新、RelOptInfo新建和清理,甚至内存分配本身。

PostgreSQL 仍然提供了部分手写的 Plugin Point,包括:

  1. 可定制的Join重排算法
  2. 可定制的PathKey生成算法
  3. 定制的Join Path生成算法

等等。

性能

虽然没有实验,但是 PostgreSQL 在优化上的性能可以想像是比较好的,这很大程度是用灵活性交换来的。

首先,不像 Volcano Optimizer ,PostgreSQL 优化器不需要不断生成中间节点,它的 RelOptInfo 的数量是相对稳定的(约等于Join的数量)。它的最优计划搜索以 RelOptInfo 为单位,如果 Join 重排不产生大量 RelOptInfo ,搜索宽度很低。

其次,RelOptInfo 简化了大量跨 Relational Expression 优化的细节,比起 Calcite 这种按 Relational Expression 来组织等价路径集合的方案, 它的搜索宽度进一步降低了。从等价集合的数量看, PostgreSQL 的搜索宽度大概比 Calcite 要低一个数量级,当然,如上所述,这是用更多优化可能性作为交换的。

最后,PostgreSQL 在优化阶段糅合了很多业务逻辑,在提高代码阅读的难度同时,也相应加快的优化效率。在优化过程中,PostgreSQL会不间断地做常量折叠、PathKey去重、Union打平、子查询打平……这些操作不会应用在memo里。

对比 Calcite/Orca ,PostgreSQL 的优化更快,更适合事务性场景。不过我无法判断 Calcite/Orca 在做了适当的剪枝和优化规则糅合后,是否也能支持事务场景。


原文链接
本文为云栖社区原创内容,未经允许不得转载。

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

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

相关文章

spring-security登录和权限管理

spring security spring security 主要的两个功能是认证和授权 认证的大概流程: Username password AuthenticationFilter(自定义usernamepassword拦截器) UserDetailService (查询用户密码的service接口) Userdetail…

官宣!阿里Blink和Flink合并计划出炉

apache已公开合并计划,点击可阅读原文《Batch as a Special Case of Streaming and Alibabas contribution of Blink》,由AI前线进行了翻译。 **春节前一周,经过社区内部讨论,阿里巴巴大数据引擎 Blink 作为 Flink 的分支 正式开源…

第四章、项目整合管理【PMP】

文章目录1. 简介2. 项目整合管理涉及的方面3. 项目整合管理的过程包括2. 制定项目章程3. 制定项目章程:输入4. 制定项目管理计划5. 指导和管理项目工作6. 管理项目知识7. 监督项目工作8. 监控项目工作9. 实施整体变更控制10. 结束项目或阶段1. 简介 项目整合管理是…

龙芯新款处理器发布;Citrix 产品曝“惊天漏洞”,影响全球 8 万家公司; AMD发布年度5大里程碑 ……...

关注并标星星CSDN云计算 速递、最新、绝对有料。这里有企业新动、这里有业界要闻,打起十二分精神,紧跟fashion你可以的!每周两次,打卡即read更快、更全了解泛云圈精彩newsgo go go铠侠(东芝)开发新型闪存&a…

开源SQL-on-Hadoop系统一览

引言 查询分析是大数据要解决的核心问题之一,而SQL作为查询分析中使用最简单、最广泛的的语言之一,必然而然的催生了许多支持在Hadoop上使用SQL的系统,这就是所谓的SQL-on-Hadoop系统,其中大众熟知的Hive就是最早的SQL-on-Hadoop…

PL/SQL中查询Oracle大数(17位以上)时显示科学计数法的解决方法

PL/SQL查询时,如果Number(17)以上的大数,会显示为科学计数法 解决方法: TOOLS->PREFERENCES->WINDOW TYPE->SQL WINDOW下选中Number fields to_char即可。

虎牙直播在微服务改造方面的实践和总结

相比文字和图片,直播提供了人与人之间更丰富的沟通形式,其对平台稳定性的考验很大,那么倡导“以技术驱动娱乐”的虎牙直播(以下简称“虎牙”)是如何在技术上赋能娱乐,本文将为您介绍虎牙在DNS、服务注册、C…

区块链人才缺口明年将达顶峰,核心开发者年入百万很正常

区块链技术一直备受争议,庞氏骗局、泡沫明显、去中心化无意义,技术无法真正建立信任、区块链技术并不能真正履行货币职能、比特币矿机耗电量大、浪费资源等等。2018年,加密货币市场总价值损失超过80%,链圈就此进入阴影。区块链技术…

阿里云移动端播放器高级功能---截图和音频波形

基本介绍 如果用户对视频播放中的某一帧画面特别感兴趣,可以使用截图功能将这一帧视频保存起来。另外有一种场景想知道是否有声音,或者想感知声音的大小震动频率等,可以通过显示一个声音的波形来形象的表示。如下图所示: 那么播放…

AES和RSA前后端加解密

先了解AES和RSA加密算法 AES算法 1、运算速度快,在有反馈模式、无反馈模式的软硬件中,Rijndael都表现出非常好的性能。 2、对内存的需求非常低,适合于受限环境。 3、Rijndael 是一个分组迭代密码, 分组长度和密钥长度设计灵活。 4、AES标…

PMBOK第六版最新十大大知识领域ITTO思维导图-干货!

PMBOK学习过程中,ITTO(输入、工具、技术、输出)是每年必考的内容,掌握ITTO的脉络,对学习和梳理PMP非常有帮助。知道这个过程要做什么,为什么做,做完有什么成果。也是项目经理必备的技能之一。 …

这个情人节,工程师用阿里云来试着表达不一样的爱意

年轻的时候谈的恋爱就像TCP链接,恋爱时三次握手即可,可分手时却分了四次。而常常久久的爱情,更像是icmp协议,无论对方身在何处,无论是否是可靠连接,无论你何时去ping她/他,她/他都默默地响应你。…

关于java的取整/和取余%

double a10; double b5; double i (a - b)/2; System.out.println(i);输出:2.5int a10; int b5; double i (double ) (a - b)/2; System.out.println(i);输出:2.5

云+X案例展 | 金融类:金山云为新网银行重塑金融服务提供云计算动力

本案例由金山云投递并参与评选,CSDN云计算独家全网首发;更多关于【云X 案例征集】的相关信息,点击了解详情丨挖掘展现更多优秀案例,为不同行业领域带来启迪,进而推动整个“云行业”的健康发展。作为国内第三家、中西部…

对于AES和RSA算法的结合使用以及MD5加盐注册登录时的密码加密

RSA和AES结合使用 接上篇的RSA和AES算法加密之后,AES对称算法对数据量大的加密比较快,而RSA公私钥加密的话会影响加密效率,但是AES的加密与解密的密钥是一致的,导致密钥不能外泄,密钥在网络传输过程中,很有…

Unity人物移动的几种方法

Unity人物移动的几种方法 方法一:transform.Translate世界坐标系移动自身移动的案例 方法二:CharacterController.Move(vector dir)按照世界坐标轴移动按照自身坐标轴移动 方法三:CharacterController.SimpleMove&…

可应用于实际的14个NLP突破性研究成果(四)

可应用于实际的14个NLP突破性研究成果(一) 可应用于实际的14个NLP突破性研究成果(二) 可应用于实际的14个NLP突破性研究成果(三) 11.对序列建模的通用卷积和递归网络的实证评估作者:SHAOJIE …

Java裁剪压缩PNG图片,透明背景色变黑问题解决

package com.gblfy.test;import java.awt.Graphics2D; import java.awt.Image; import java.awt.Transparency; import java.awt.image.BufferedImage; import java.io.File;import javax.imageio.ImageIO;/*** 图片工具类*/ public class ImageUtil {/*** 裁剪PNG图片工具类**…

量子通信,到底是什么工作原理?

戳蓝字“CSDN云计算”关注我们哦!作者 | 小枣君责编 | 阿秃今天,小枣君要和大家聊的是“量子通信”。最开始计划写这个专题的时候,小枣君的内心是很纠结的。鲜枣课堂的目的,就是传递“普通人都能听懂”的知识。每一个知识点专题&a…

记录——oracle数据库备份

oracle数据库备份Oracle数据库的三种标准的备份方法: 1.导出/导入(EXP/IMP)。 2.热备份。 3.冷备份。 注释:导出备件是一种逻辑备份,冷备份和热备份是物理备份。 一、导出/导入(Expo…