PostgreSQL技术内幕7:PostgreSQL查询编译

文章目录

    • 0.简介
    • 1.整体过程
    • 2.查询分析
      • 2.1 Lex
      • 2.2 Yacc
      • 2.3 PG词法分析和语法分析介绍
      • 2.4 PG语义分析
    • 4.查询优化
      • 4.1 预处理
        • 4.1.1 提升子链接和子查询
        • 4.1.2 预处理表达式
        • 4.1.3 处理HAVING子句
      • 4.2 改进查询树
        • 4.2.1 路径生成
        • 4.2.2 代价估计
      • 4.3 计划生成

0.简介

一次完整的SQL执行包含两大部分,一个是查询编译涉及到的内容较多,整体分为查询分析,查询重写和查询优化。结束后交给执行器其进行编译的执行,本节将整体介绍一次查询的流程以及详细分析PG查询编译的三个步骤。

1.整体过程

先看一次常规的查询流程,收到查询语句后经过解析器和转换器,得到相应的关系代数表达式,然后经过优化器得到最后的执行计划,最后执行后输出结果。
在这里插入图片描述
对于PG,后台的backend收到命令后,首先会调用查询分析模块,然后根据命令类型来做处理,如果是创建表,创建用户这种会直接发送给功能性命令处理模块处理,如果是select,insert等,则需要构建查询树,然后进行查询重写和查询优化,然后交给执行器执行。
在这里插入图片描述
代码调用流程如下:


exec_simple_query->pg_parse_query->raw_parser->pg_analyze_and_rewrite->parse_analyze->pg_rewrite_query->pg_plan_querie

在这里插入图片描述
代码目录结构如下:
在这里插入图片描述

2.查询分析

查询分析包含词法分析,语法分析以及语义分析三部分。PG使用Lex与Yacc实现词法分析和语法分析。一条SQL语句在函数pg_parse_query中经过词法分析和语法分析得到解析树(PG中的Query结构)。

2.1 Lex

Lex 即为 Lexical Analyzar,是一种生成扫描器的工具。扫描器是一种识别文本中的词汇模式的程序。这些词汇模式(或者常规表达式)在一种特殊的句子结构中定义。定义了正则表达式匹配规则的Lex文件(后缀名为".l")可以转换为为C语言源代码文件。一个Lex文件分为三段,各段之间使用"%%"分隔:

1)定义段:包含任意的C语言头文件、符号说明等,这部分会被直接拷贝到生成文件当中;

2)规则段:正则表达式的匹配规则,每当成功匹配一个模式,就对应其后"{}"中的代码;

3)代码段:可以是任意的C语言代码,但是必须要调用Lex提供的函数,因为这里是Lex的入口函数,完成实际的分析功能。
一个例子:


//字数统计
%{int wordCount = 0;%}chars [A-za-z\_\'\.\"]numbers ([0-9])+delim [" "\n\t]whitespace {delim}+words {chars}+%%

2.2 Yacc

Yacc 即为 Yet Another Compiler Compiler。其 GNU 版叫做 Bison。它是一种工具,将任何一种编程语言的所有语法翻译成针对此种语言的 Yacc 语 法解析器。它用巴科斯范式(BNF, Backus Naur Form)来书写。Yacc 文件一般带有 .y 后缀。其也分为三个段,段之间使用%%分隔。
1)定义段:可以是C代码,包含头文件以及函数声明,同时也可以定义Yacc的内部标志等;
2)规则段:语法规则,每当成功匹配一个语法后,就对应其后面"{}“中的代码。其中”$$“标识语法表达式中左边结构的值(类似左值),而”$1"表示语法表达式右边结构第一个标识符对应的值,以此类推;

3)代码段:包含C代码,同样地也必须包含一些Yacc函数和Lex传递给Yacc的变量。
一个例子:


%typedef char* string;#define YYSTYPE string%}%token NAME EQ AGE%%file : record file| record;record : NAME EQ AGE {printf("%s is %s years old!!!\n", $1, $3); };%%int main(){yyparse();return 0;}int yyerror(char *msg){printf("Errorencountered: %s \n", msg);}

2.3 PG词法分析和语法分析介绍

  1. kwlist.h: 声明keyword列表。可以简单看几个

PG_KEYWORD("copy", COPY, UNRESERVED_KEYWORD)
PG_KEYWORD("cost", COST, UNRESERVED_KEYWORD)
PG_KEYWORD("create", CREATE, RESERVED_KEYWORD)
PG_KEYWORD("cross", CROSS, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("csv", CSV, UNRESERVED_KEYWORD)

2)keywords.c: 定义ScanKeywordLookup函数实现,通过二分查找输入是否为关键字,如果为关键字,返回指针,不是则返回NULL(这依赖于上面的kwlist中声明是有序的)。


const ScanKeyword *
ScanKeywordLookup(const char *text,const ScanKeyword *keywords,int num_keywords)
{int      len,i;char    word[NAMEDATALEN];const ScanKeyword *low;const ScanKeyword *high;len = strlen(text);/* We assume all keywords are shorter than NAMEDATALEN. */if (len >= NAMEDATALEN)return NULL;/** Apply an ASCII-only downcasing.  We must not use tolower() since it may* produce the wrong translation in some locales (eg, Turkish).*/for (i = 0; i < len; i++){char    ch = text[i];if (ch >= 'A' && ch <= 'Z')ch += 'a' - 'A';word[i] = ch;}word[len] = '\0';/** Now do a binary search using plain strcmp() comparison.*/low = keywords;high = keywords + (num_keywords - 1);while (low <= high){const ScanKeyword *middle;int      difference;middle = low + (high - low) / 2;difference = strcmp(middle->name, word);if (difference == 0)return middle;else if (difference < 0)low = middle + 1;elsehigh = middle - 1;}return NULL;
}

3)scansup.c:实现词法分析时常见的函数。
4) scan.l:Lex文件,通过编译编译生成scan.c。
5) gram.y:Yacc文件,通过编译生成gram.c。
6)check_keywords.pl: 检查在gram.y和kwlist.h中定义的关键字列表一致性
7)parser.c: 解析的入口,定义raw_parser函数
通过以上文件处理raw_parser会返回一个List ,也就是为每个SQL命令都返回一个解析树。

2.4 PG语义分析

语义分析会检查命令中是否包含不符合语义规定的元素,如表和字段是否存在,函数是否可用。因此语义分析需要访问到数据库中的系统表,从而获得查询表的OID以及查询字段的属性等。
语义分析的入口函数是pg_analyze_and_rewrite,其将词法分析与语法分析处理后得到的parsetree_list中的每棵树都进行语义分析与重写。其中负责语义分析的函数为parse_analyze,该函数对parse_tree进行语法分析并转换为一棵查询树(以Query结点的形式存在)。parse_tree函数中涉及的两个重要的结构体分别为Query和ParseState,其中Query用于存储查询树而ParseState则用于存储语义分析的中间信息,比如是否是子查询,查询涉及的表等,结构如下。Query的生成可以参考transformSelectStmt()的流程,解析SelectStmt结构生成一个查询树,将其各部分挨个分析填入Query。


typedef struct Query
{NodeTag    type;CmdType    commandType;  /* select|insert|update|delete|utility */QuerySource querySource;  /* where did I come from? */uint64    queryId;    /* query identifier (can be set by plugins) */bool    canSetTag;    /* do I set the command result tag? */Node     *utilityStmt;  /* non-null if commandType == CMD_UTILITY */int      resultRelation; /* rtable index of target relation for* INSERT/UPDATE/DELETE; 0 for SELECT */..........             /* other */int      stmt_location;  /* start location, or -1 if unknown */int      stmt_len;    /* length in bytes; 0 means "rest of string" */
} Query;struct ParseState
{struct ParseState *parentParseState;  /* stack link */const char *p_sourcetext;  /* source text, or NULL if not available */List     *p_rtable;    /* range table so far */List     *p_joinexprs;  /* JoinExprs for RTE_JOIN p_rtable entries */List     *p_joinlist;    /* join items so far (will become FromExpr* node's fromlist) */List     *p_namespace;  /* currently-referenceable RTEs (List of* ParseNamespaceItem) */bool    p_lateral_active;  /* p_lateral_only items visible? */......                     /* other*/
}

3.查询重写

从整体流程中我们已经知道,PG会对完成查询分析的非功能性的语句进行查询重写。查询重写是根据规则来进行的,其规则存储在pg_rewrite表中。


select rulename,ev_enabled,ev_type from pg_rewrite;

在这里插入图片描述
各个字段含义介绍:
在这里插入图片描述
可以认为一条规则(pg_rewrite的一个元组)是在目标表(ev_class)上执行符合条件(ev_qual)的特定动作(ev_type)时,将用规则动作(ev_action)代替原始的动作或者将规则的动作附加在原始命令之前或之后。
PG中规则分为两类:

1)ev_type:可以分为SELECT,UPDATE,INSERT,DELETE;

2)is_instead:INSTEAD(true)或ALSO(false)
根据需要来进行不同元组的创建就可以创建不同的重写规则,这也体现了PG的扩展性。

4.查询优化

查询优化分为三个步骤:1.预处理,处理查询树,主要是提升子链接和子查询以及having子句等;2.根据改进的查询树,利用特定算法来生成最优连接路径和候选路径列表;3.根据最优路径,先生成基本查询计划,然后group by和having等子句的计划节点,生成最后的计划。
查询计划的入口函数是pg_plan_queries,调用关系如下:

exec_simple_query->pg_plan_queries->pg_plan_query->planner->standard_planner->subquery_planner// 预处理->preprocess_xxx->pull_up_sublinks->pull_up_subqueries// 生成计划树->grouping_planner->query_planner->SS_finalize_plan

4.1 预处理

预处理阶段时主要负责消除冗余条件,减少递归层数(通过提升子链接与子查询实现)以及简化路径生成等。

4.1.1 提升子链接和子查询

子链接和子查询的区别:子查询是一条完整的查询语句,而子链接是一条表达式,但是表达式内部也可以包含查询语句。也就是说,子查询是放在FROM子句里的而子链接则出现在WHERE子句或者HAVING子句中。PG支持嵌套查询的SQL写法,即FROM子句中可以包含一个SELECT查询语句。原始执行下,会先执行子查询(内部),再执行父查询。但将子查询提升后,可以与父查询共同优化,从而提高查询的效率。
提升子链接的入口函数是pull_up_sublinks,其内部会调用pull_up_sublinks_jointree_recurse函数递归地处理jointree,然后调用pull_up_sublinks_qual_recurse处理约束条件。

提升子查询的入口函数是pull_up_subqueries,其内部会调用pull_up_subqueries_recurse函数递归地处理子查询。提升子查询分为三种情况处理:
1)范围表存在子查询。如果是简单的子查询,那么调用函数pull_up_simple_subquery直接提升,而如果是简单的UNION ALL子查询,那么调用pull_up_simple_union_all直接提升;
2)FROM表达式存在子查询。调用pull_up_subqueries_recurse进行递归处理;
3)连接表达式中的子查询。调用pull_up_subqueries_recurse进行递归处理。

4.1.2 预处理表达式

表达式可以是一个目标链表,一个WHERE语句,一个HAVING谓语或者一些其它的东西。在PG中表达式的预处理由函数preprocess_expression完成,其主要作用是:
1)连接别名使用基本关系变量替换;
2)简化常量表达式;
3)规范表达式内容;
4)将子链接转化为子计划,该转换通过函数make_subplan实现。

4.1.3 处理HAVING子句

对于HAVING子句来说,除了进行前面所提到的预处理外,还需要处理其中的每个条件。如果HAVING子句中没有聚集函数的话,那么它完全可以退化到WHERE子句中去,否则的话它将被写到查询树的HavingQual字段里面。

4.2 改进查询树

4.2.1 路径生成

生成路径工作由函数query_planner来完成,整体流程如下:
在这里插入图片描述
因为单表有多种访问方式(索引访问,顺序访问等)、表和表直接有着多种连接方式和连接顺序,所以就算基本表一致,但最后访问最终表的路径可能有着多种,所以优化器考虑了所有可能的路径,并选择最优路径来生成查询执行计划。PG中生成执行计划的算法是动态规划(小规模优化)与遗传算法(大规模优化):
1)动态规划:在PG中,使用动态规划获得最优路径,主要分为三步:1. 初始化,为每个基本表生成访问路径;2. 状态传递,从基本表开始向前生成连接表与计算该连接表路径需要的代价,并保留其中代价评估最优的路径;3. 传递到最后表时,选出其中最优的路径。保留的路径需要满足以下条件:启动代价最小;总执行代价最小;路径的输出排序键。
2)遗传算法:当表格的数量过多时,遍历所有的表需要消耗大量的时间和内存空间。因此,PG提供了遗传算法来减少需要遍历的路径,从而提高查找路径的效率。不过遗传算法只能找到一个准最优的路径。

4.2.2 代价估计

路径的效率与其在执行过程中需要的CPU时间以及磁盘存取非常相关,因此PG在文件"src/backend/optimizer/path/costsize.c"中定义了一些关于磁盘I/O以及CPU的估算代价:
seq_page_cost:顺寻存取页的代价,值为1.0。
random_page_cost:非顺序存取页的代价,值为4.0。
cpu_tuple_cost:典型的CPU处理一个元组的代价,值为0.01。
cpu_index_tuple_cost:典型的CPU处理一个索引元组的代价,值为0.005。
cpu_operator_cost:CPU处理一个典型的WHERE操作的代价,值为0.0025。
effective_cache_size:用来度量PG和OS缓存的磁盘页的数量,值为16384
一条路径的代价与磁盘中存储的元组数量及元组占用的页数相关,其估计路径代价的步骤主要如下:1. 根据统计信息与查询条件估算本次查询需要的I/O次数以及获取的元组个数,并得到估算的磁盘代价;2. 根据元组数量计算需要的CPU代价;3. 综合考虑磁盘代价与CPU代价。

4.3 计划生成

在得到最优路径后,规划器会根据该路径生成对应的计划。PG中生成计划的代码文件是"backend/optimizer/plan/createplan.c",其提供的入口函数是create_plan,里面包含了顺序扫描,采样扫描,索引扫描,TID扫描等计划的生成。

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

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

相关文章

STM32基础篇:PWR

PWR简介 PWR&#xff08;Power Control&#xff09;&#xff0c;为电源控制模块&#xff0c;负责管理STM32内部的电源供电部分&#xff0c;可以实现可编程电压监测器和低功耗模式的功能。 1、可编程电压监测器 简称PVD&#xff0c;可以监控VDD电源电压。当VDD下降到PVD阀值以…

爬虫入门学习

流程 获取网页内容 HTTP请求 Python Requests解析网页内容 HTML网页结构 Python Beautiful Soup储存或分析数据 HTTP (Hypertext Transfer Protocol) 客户端和服务器之间的请求-响应协议 Get方法&#xff1a;获得数据 POST方法&#xff1a;创建数据 HTTP请求 请求行 方法类型…

rv1126-rv1109-mkcramfs-mkfs.cramfs-打包文件系统

事情是这样的: 定制了文件系统打包功能;然后我是根据这个指令 fakeroot mkfs.cramfs rootfs_glibc_rv1126/ rootfs.img mkfs.cramfs rootfs_glibc_rv1126/ rootfs.img 起因就是这个fakeroot; 不加的话打出来的rootfs.img是没有用户权限的 然后我根据fakeroot mkfs.cramfs ro…

AcWing算法基础课-785快速排序-Java题解

大家好&#xff0c;我是何未来&#xff0c;本篇文章给大家讲解《AcWing算法基础课》785 题——快速排序。这篇文章介绍了使用快速排序算法对整数数列进行排序的方法&#xff0c;包括选择基准元素、分区操作和递归排序子数组。通过详细的步骤和示例&#xff0c;解释了快速排序的…

MySQL之数据库基础

目录 一、数据库 1、基本概念 2、常见的数据库 3、MySQL数据库 连接MySQL服务器 数据逻辑存储 二、数据库和表的本质 三、SQL语句 四、服务器&#xff0c;数据库&#xff0c;表的关系 五、存储引擎 查看存储引擎 一、数据库 1、基本概念 一般来说&#xff0c;数据库…

es映射配置(_mapping)

文章目录 1、创建映射字段2、查看映射关系 1、创建映射字段 PUT /索引库名/_mapping {"properties": {"字段名": {"type": "类型","index": true&#xff0c;"store": true&#xff0c;"analyzer": &q…

视频结构化从入门到精通——视频结构化主要技术介绍

视频结构化主要技术 1 视频接入 “视频接入”是视频结构化管道的起点&#xff08;SRC Point&#xff09;视频接入是视频结构化处理的第一步&#xff0c;它涉及将视频数据从各种采集源获取到系统中进行进一步处理。视频接入的质量和稳定性对后续的数据处理、分析和应用至关重要…

多参数遥测终端科技守护水电站生态流量下泄

随着我国水电事业的蓬勃发展&#xff0c;水电站在推动地方经济快速增长、缓解能源压力方面发挥了不可替代的作用。然而带来的生态环境问题日益凸显&#xff0c;因水电站下泄流量不足造成部分河段减水、脱水甚至干涸&#xff0c;影响了河流的正常生态功能和居民的生产、生活。因…

【硬件操作入门】2--GPIO与门电路、二极管三极管、LED电路与操作

【硬件操作入门】2–GPIO与门电路&#xff08;二极管&三极管&#xff09;、LED电路与操作 文章目录 【硬件操作入门】2--GPIO与门电路&#xff08;二极管&三极管&#xff09;、LED电路与操作一、GPIO与门电路1.1、GPIO的应用1.2、GPIO引脚操作1.2.1 设置引脚为GPIO功能…

day39(8/29)——harbor私有仓库管理

一、harbor私有仓库管理 是python的包管理工具&#xff0c;和yum对redhat的关系是一样的 yum -y install epel-release yum -y install python2-pip pip install --upgrade pip pip list pip 8x pip install --upgrade pip pip install --upgrade pip20.3 -i https://mirror…

RFID光触发标签在文件柜管理中的创新应用

在当今信息化时代&#xff0c;文件管理对于企业和机构的重要性不言而喻。传统的文件柜管理方式存在诸多问题&#xff0c;如查找文件困难、管理效率低下、安全性难以保障等。而 RFID 光触发标签技术的出现&#xff0c;为文件柜管理带来了全新的解决方案。 一、传统文件柜管理的…

Spring扩展点系列-@PostConstruct

简介 spring的Bean在创建的时候会进行初始化&#xff0c;而初始化过程会解析出PostConstruct注解的方法&#xff0c;并反射调用该方法。 PostConstruct 的使用和特点 只有一个非静态方法能使用此注解&#xff1b;被注解的方法不得有任何参数&#xff1b;被注解的方法返回值必…

实际开发中git在IDEA中的使用

相信搜索这个的同学代码都已经拉取到本地了&#xff0c;并且已经在idea中打开了。 1.一般我们从远程colone下来的代码默认是在主分支下的&#xff0c;也就是说我们从远程的主分支拉取的代码并且在本地创建了一个主分支。 2.一般主分支是不允许修改的&#xff0c;所以我们可以基…

SpringBoot2:配置绑定与自动配置功能源码解读

一、配置绑定 1、作用说明 我们在开发springboot项目时&#xff0c;会有个配置文件&#xff0c;application.properties文件。 我们知道&#xff0c;像什么访问端口、上传功能的相关配置&#xff0c;都会在这里进行配置。 而这些&#xff0c;都是springboot自带的或者第三方j…

Linux | 匿名管道和命名管道:进程间通信数据流的桥梁

目录 1、进程间通信目的 2、管道——匿名管道和命名管道 匿名管道 匿名管道的示例代码&#xff1a;将数据写入管道、子进程从管道读取数据并将其输出到bash中 父子进程通过匿名管道建立通信 重点&#xff1a;管道的五个特点 命名管道&#xff08;也称为FIFO&#xff09;…

每日一题,零基础入门FPGA——工程师在线精讲,直播预告

题目传送门&#xff1a;F学社 zzfpga.com/StudentPlatform/Sheet/QuestionBankhttp://zzfpga.com/StudentPlatform/Sheet/QuestionBank 【第Ⅰ期题目 * 5】 请使用D触发器和必要的逻辑门实现此同步时序电路&#xff0c;用Verilog语言描述。 【第Ⅰ期题目 * 4】 请设计一个0…

观测云核心技术揭秘:基于时间的半结构化数据模型

前言 众所周知&#xff0c;真正意义上的统一监控观测平台本质上是一个超大的数据湖&#xff0c;其存储了大量的来自监控指标&#xff0c;各种各样的日志&#xff0c;各种各样的链路追踪以及包括用户访问行为等海量的可观测性数据。 这些海量数据有什么特点呢&#xff1f; 首先…

【网络安全】逻辑漏洞:绕过应用程序重要功能

未经许可,不得转载。 文章目录 正文漏洞影响正文 目标:xxx.com 一个流行的汽车平台,允许用户为经销商留下评论。该平台有一个功能,用户可以点赞评论,并且它限制每个用户对每个评论只能点赞一次。 然而,我找到了绕过的方法(并不是并发)。 在点击“点赞”按钮时拦截请…

新手指南 | 研发人员奖金激励方案步骤实操

研发团队是一个非常独特的存在。在研发人员的薪酬、激励方案设计上也是很多HR的痛点。 毕竟工作变化大&#xff0c;职责变化快、个体能力差异大、投入了不一定会有产出、多线程并行&#xff0c;贡献难判定、知识密度高&#xff0c;价值难衡量等等... 基于以上难点&#xff0c…

【系统架构设计】嵌入式系统设计(1)

【系统架构设计】嵌入式系统设计&#xff08;1&#xff09; 嵌入式系统概论嵌入式系统的组成硬件嵌入式处理器总线存储器I/O 设备与接口 软件 嵌入式开发平台与调试环境交叉平台开发环境交叉编译环境调试 嵌入式系统概论 嵌入性、专用性、计算机系统是嵌入式系统的三个基本的核…