十八. 内部结构
18.1 PostgreSQL的内部概述
1、查询经过的路径
- 建立连接
- 分析器阶段
- 重写系统
- 规划器/优化器
- 执行器阶段
2、如何建立连接
PostgreSQL是用一个简单的“每用户一进程”的client/server模型来实现的。在这种模式里,一个客户端进程只与一个服务器进程连接,由于不知道具体要建立多少个连接,所以不得不利用一个主进程在每次连接请求时都派生出一个新的服务器进程来,这个主进程叫做postgres,它监听着一个特定的TCP/IP端口等待进来的连接。
主进程每当检测到一个连接请求时,postgres进程派生出一个新的服务器进程。服务器进程之间使用信号灯和共享内存进行通讯,以确保在并发的数据访问过程中的数据完整性。客户端进程可以是任何理解PostgreSQL协议的程序。
应用程序一旦与PostgreSQL服务器建立起来连接,客户端进程就可以向后端(服务器)进程发送查询了。查询是通过纯文本传输的,也就是说在前端(客户端)不做任何分析处理。服务器分析查询,创建执行规划,执行该规划并且通过已经建立起来的连接把检索出来的数据行返回给客户端。
3、分析器阶段
- 分析器
分析器必须检查查询字符串的语法。如果语法正确,则创建一个分析树并将其返回,否则,将返回一个错误。实现分析器和词法器使用了著名的Unix工具yacc和lex。 - 转换处理
分析器阶段只使用与SQL语法结构相关的固定规则来创建分析树。由于分析器不会查找任何系统表,因此它不可能理解请求查询的详细含义。在分析器技术之后,转换处理分析器传过来的分析树,再做进一步的处理,即:解析哪些查询中引用了哪个表、哪个函数、哪个操作符,最后再生成表示这个信息的数据结构,该数据结构就是查询树。
4、PostgreSQL规则系统
5、规划器/优化器
- 规划器/优化器概述
规划器/优化器的任务是创建一个优化了的执行规划。如果可能,查询优化器将检查每个可能的执行规划,最终选择运行最快的执行计划。 - 生成可能的规划
规划器/优化器通过为扫描查询里出现的每个关系生成规划,可能的规划是由每个关系上由哪些可用的索引决定的。对一个关系总是可以进行一次顺序查找,所以总是会创建只使用顺序查找的规划。假设一个关系上定义着一个索引(例如B-tree索引),并且一条查询包含约束 relation.attribute OPR constant。如果relation.attribute碰巧匹配B-tree索引的关键字,那么将会创建另一个使用B-tree索引扫描该关系的规划。如果还有别的索引,而且查询里面的约束又和那个索引的关键字匹配,则还会生成更多的规划。
6、执行器
18.2 PostgreSQL的内部系统表
1、数据表
大多数数据表都是在数据库创建的过程中从模版数据库中拷贝过来的,这些表与数据库是相关的。
- pg_aggregate
pg_aggregate表用于存储与聚集函数有关的信息。聚集函数是对一个数值集进行操作的函数,它返回从这些值中计算出的一个数值,一般情况下,数值集通常是指每个匹配查询条件的行中的一个字段。 - pg_am
pg_am存储有关索引访问方法的信息,系统支持的每种索引访问方法都有一行。 - pg_amop
pg_amop表存储有关和索引访问方法操作符类关联的信息。如果一个操作符是一个操作符类中的成员,那么在这个表中会占据一行。
2、系统视图
- pg_available_extensions
pg_available_extensions视图列出了可用于安装的扩展,该视图是只读的。 - pg_cursors
pg_cursors列出了当前可用的游标。 - pg_locks
pg_locks提供有关在数据库服务器中由打开的事务持有的锁的信息。pg_locks对每个活跃的可锁定对象、请求的锁模式、以及相关的事务保存一行。
18.3 PostgreSQL的内部前端/后端协议
1、概述
PostgreSQL为了可以有效地为多个客户端提供服务,服务器为每个客户端派生一个新的“后端”进程。在检测到连接请求后,马上创建一个新的子进程。不过,这些是对协议透明的。对于协议而言,术语“后端”和“服务器”是可以互换的;“前端”和“客户机”也是可以互换的。
2、消息流
在PostgreSQL内部,所有通讯都是通过一个消息流进行的。消息的第一个字节标识消息类型,后面的四个字节声明消息剩下部分的长度,这个长度包括长度域自 身,但不包括消息类型字节。剩下的消息内容由消息类型决定。因连接状态的不同,存在几种不同的子协议:启动、查询、函数调用、COPY、结束。还有用于通知响应和命令取消的特殊信息,这些特殊信息可能在启动阶段过后的任何时间产生。
3、消息数据类型
- Intn(i)
一个网络字节顺序的n位整数。如果声明了i,它将会出现确切的值,否则这个数值就是一个变量。 - Intn[k]
一个k个n位整数元素的数组,每个都是以网络字节顺序存储的。数组长度k是由消息前面的字段来判断的。
3.String(s)
一个以零结尾的字符串。 - Byten©
精确的n字节。如果声明了c那么它是确切的数值。
4、错误和通知消息字段
S:表示严重性。
C:表示代码。
M:表示消息。
D:表示细节。
H:表示提示。
P:表示位置。
Q:表示内部查询。
W:表示哪里。
F:表示文件。
L:表示行。
R:表示过程。
18.4 PostgreSQL的编码约定
1、格式
代码格式使用每个制表符4列的空白,也就是说制表符不被展开为空白。每个逻辑缩进层次都是更多的一个制表符,布局规则遵循BSD传统。
src/tools目录包含了适用于Emacs的示范配置文件,文本浏览工具more和less可以用下面命令调用。
more -x4
less -x4
2、报告服务器里的错误
3、错误消息风格指南
- 主信息简短
- 格式
- 引号
- 使用引号
- 语法和标点
- 大写字符与小写字符比较
- 避免被动语气
- 现代时与过去时的比较
- 对象类型
- 方括弧
- 组装错误信息
- 错误的原因
消息应该总是说明为什么发生错误。 - 函数名
不要在错误信息里包含报告过程的名字。 - 尽量避免的字眼
尽量避免的字眼包括不能、坏的、 非法、未知等。 - 正确地拼写
用单词的全拼。避免对单词进行缩写。 - 本地化
错误信息文本是需要翻译成其它语言的,因此,语句应该本地化。
18.5 基因查询优化器
1、作为复杂优化问题的查询处理
2、基因算法
3、PostgreSQL里的基因查询优化(GEQO)
GEQO模块是试图解决类似漫游推销员问题(TSP)的查询优化问题。可能的查询规划被当作整数字符串进行编码。每个字符串代表查询里面一个关系到下一个关系的连接的顺序。
18.6 索引访问方法接口定义
1、索引的系统表记录
2、索引访问方法函数
索引访问方法必须提供的索引构造和维护函数有:
IndexBuildResult *
ambuild (Relation heapRelation,
Relation indexRelation,
IndexInfo *indexInfo);
3、索引扫描
4、索引唯一性检查
5、索引开销估计函数
18.7 GiST索引
1、GiST简介
2、GiST的可扩展性
通常,实现一种新的索引访问方法意味着大量的艰苦工作。必须理解数据库的内部工作机制,比如锁的机制和预写日志。GiST接口有一个高层的抽像,只要求访问方法的实现者实现被访问的数据类型的语意。GiST层本身会处理并发,日志和搜索树结构的任务。
不要把这个扩展性和其它标准搜索树的扩展性混淆在一起,比如它们所能处理的数据等方面。
简单说,GiST组合了扩展性和通用性,以及代码复用和一个干净的界面。
3、实现方法
consistent。这个方法给出一个在树的数据页上的谓词p和一个用户查询q,如果对于一个给定的数据项,p和q 都很明确地不能为真,那么这个方法将返回假。
union 。这个方法合并树中的信息。给出一个条目的集合,这个函数生成一个新的谓词,这个谓词对所有这些条目都为真。
compress。这个方法将数据项转换成一个适合于在一个索引页里面物理存储的格式。
decompress。这个方法是compress方法的反方法。把一个数据项的索引表现形式转换成可以由数据库操作的格式。
penalty 。这个方法返回一个表示将新条目插入树中特定分支需要的"开销"的数值。项将会按照树中最小 penalty 的路径插下去。
picksplit。如果需要分裂一个页面的时候,这个函数决定页面中哪些条目保存呆旧页面里,而哪些移动到新页面里。
same。如果两个条目相同,返回真,否则返回假。
18.8 数据库的物理存储
1、数据库文件布局
2、TOAST
3、数据库分页文件
序列和TOAST的格式与普通表一样。
项指的是存储在一个页面里的独立数据值。在一个表里,一个项是一个行;在一个索引里,一个项是一条索引记录。
每个表和索引都以固定尺寸(通常是 8K ,但也可以在编译时选择其它尺寸)的页面数组存储。在表里,所有页面逻辑都相同,所以一个特定的项(行)可以存储在任何页面里。在索引里,第一个页面通常保留为元页面,保存着控制信息,并且依索引访问方法的不同,在索引里可能有不同类型的页面。
18.9 BKI后端接口
1、BKI文件格式
BKI输入是由一系列命令组成的。命令是由一些记号组成的,具体情况则由命令语法决定。记号通常是用空白分隔的,但是如果没有歧义的话可以不要。没有什么特殊的命令分隔符。通常会把一条新的命令放在新的一行上以保持清晰。记号可以是某些关键字,特殊字符(圆括弧,逗号等),数字,或者双引号字符串。注意,所有命令都是区分大小写的。
2、BKI命令
(1) create [bootstrap] [shared_relation] [without_oids] tablename tableoid (name1 = type1 [, name2 = type2, …])
(2) open tablename
(3) close [tablename]
(4) insert [OID = oid_value] ( value1 value2 … )
(5)declare [unique] index indexname indexoid on tablename using amname ( opclass1 name1 [, …] )
(6)declare toast toasttableoid toastindexoid on tablename
(7)build indices
3、系统初始化的BKI文件结构
⑴ create bootstrap其中一个关键表。
⑵ insert数据,这些数据至少描述这些关键表本身。
⑶ close。
⑷ 重复创建和填充其它关键表。
⑸ create(不带 bootstrap)一个非关键表。
⑹ open。
⑺ insert 需要的数据。
⑻ close。
⑼ 重复创建其它非关键表。
⑽ 定义索引。
⑾ build indices。
4、例子
下面的命令集将创建名为 test_table 的表,该表有两个类型分别为int4和text的字段cola和colb,然后向该表插入两行。
create test_table 420 (cola = int4, colb = text)
open test_table
insert OID=421 ( 1 “value1” )
insert OID=422 ( 2 null )
close test_table

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/55191.html