1.PostgreSQL-源码学习笔记(5)-索引
2.源码 | 为金融场景而生的源码数据类型:Numeric
3.PostgreSQL14基于源码安装和入门教程
4.干货 | PostgreSQL数据表文件底层结构布局分析
5.PostgreSQL内核Trigger的一生
6.PostgreSQL 技术内幕(十七):FDW 实现原理与源码解析
PostgreSQL-源码学习笔记(5)-索引
索引是数据库中的关键结构,它加速了查询速度,解析尽管会增加内存和维护成本,源码但效益通常显著。解析在PG中,源码索引类型丰富多样,解析易语言FOR源码包括B-Tree、源码Hash、解析GIST、源码SP-GIST、解析GIN和BGIN。源码所有索引本质上都是解析独立的数据结构,与数据表并存。源码
查询时,解析没有索引会导致全表扫描,源码效率低下。创建索引可以快速定位满足条件的元组,显著提升查询性能。PG中的索引操作函数,如pg_am中的注册,为上层模块提供了一致的接口,这些函数封装在IndexAmRoutine和IndexScanDesc中。
B-Tree索引采用Lehman和Yao的算法,每个非根节点有兄弟指针,页面包含"high key",用于快速扫描。PG的B-Tree构建和维护流程涉及BTBuildState、spool、元页信息等结构,包括创建、插入、扫描等操作。
哈希索引在硬盘上实现,支持故障恢复。它的页面结构复杂,包括元页、桶页、溢出页和位图页。插入和扫描索引元组时,需要动态管理元页缓存以提高效率。
GiST和GIN索引提供了更大的灵活性,支持用户自定义索引方法。GiST适用于通用搜索,而GIN专为复合值索引设计,支持全文搜索。它们在创建时需要实现特定的访问方法和函数。
尽管索引维护有成本,但总体上,它们对提高查询速度的价值不可忽视。了解并有效利用索引是数据库优化的重要环节。
源码 | 为金融场景而生的数据类型:Numeric
高日耀,资深数据库内核研发人员,毕业于华中科技大学。锐捷源码他专注于研究主流数据库架构与源码,长期参与分布式数据库内核研发。他的专业领域包括分布式 MPP 数据库 CirroData 内核开发(东方国信)与 MySQL 系列产品内核开发(青云科技)。
在数据库设计和源码实现领域,高日耀曾经参与过数据类型(如 Numeric、Datetime、Timestamp、varchar 等)的设计与实现。他特别深入研究了 Numeric 类型,这个标准 SQL 的一部分,与 Decimal 类型等价,主要用于金融场景,存储大数值,对数据的精度有极高的要求。
以下内容基于 PostgreSQL 源码,解析了 PostgreSQL 中 Numeric 类型的内存计算结构和磁盘存储结构。
在编程过程中,我们通常使用内置的 4 字节 float 和 8 字节 double 类型进行加减乘除运算。然而,浮点数通过科学计数法存储,在二进制与十进制转换过程中,对于某些二进制数,其精度会有缺失。而金融场景中动辄处理巨大数值,且对精度要求极高,任何微小的精度损失都是不可接受的。市面上的数据库基本都包含了 Numeric 类型,通过字符串精确存储每一位数,确保浮点数无法达到的精确计算。
以下为 Numeric 类型的语法简介:
NUMERIC(precision, scale)
例如:.,其中 precision 为 5,scale 为 3。
在不指定精度的情况下,数值类型的取值范围如下:
以下是 Numeric 类型的特殊值——NaN(代表 "not-a-number")。在 SQL 中作为常量使用时,需要加上引号,例如:
在 SQL 中,Numeric 数据的流向涉及数据库执行流程,包括创建表、插入数据等操作。下面以创建 test 表并插入数据为例,关注写入 Numeric 数字的内存表示、定义为 NUMERIC(5,2) 的数据结构在内存中的表示方式,以及数据写入磁盘后的存储结构。
数据在内存中的存储结构与落盘时的存储结构不同,落盘时需要去掉内存中所占用的无效字节。例如,varchar() 在内存中分配了 个字节,而实际只写入了 "abc" 三个字节,因此,尽管内存中分配了 个字节,落盘时实际上只使用了 3 个字节。尺寸表源码如果数据量非常大,直接写入磁盘而不进行处理,将会浪费大量磁盘空间。
接下来,我们将解析 Numeric 类型在磁盘上的存储结构。结构体 NumericData 包含了 NumericLong 和 NumericShort 的 union 字段,用于描述最终写入磁盘的结构。下面详细介绍这些结构体的组成部分。
在后续文章中,我们将基于内存计算结构,深入探讨 Numeric 类型在代码中的实现原理,通过数学公式解析二进制与十进制转换为何会产生精度损失的问题。此外,我们还将继续解析 MySQL / Oracle 等数据库中 Numeric 类型的设计与源码实现。
PostgreSQL基于源码安装和入门教程
PostgreSQL 源码安装入门教程
本文将引导您在openEuler . LTS-SP3系统上基于源码安装并配置PostgreSQL ,包括操作系统环境设置、网络配置、软件包安装、用户和数据盘创建,以及数据库的初始化、启动和管理。1.1 操作系统环境
安装openEuler后,确保系统安装了bc命令(若缺失,后续会安装)。1.2 网络配置
通过Nmcli配置网络,首先检查并设置网络接口ens的IP地址,无论是自动获取还是静态配置。1.3 更新系统与工具安装
更新软件包并安装bc、vim、tmux和tar等工具,以支持后续操作。1.4 用户与数据盘创建
创建postgres用户和用户组,以及可能的专用数据盘,如NVMe SSD,用于提高性能。2. 安装与配置
2.1 下载与解压
以root权限下载并解压PostgreSQL 的源代码压缩包。2.2 安装与初始化
按照指导进行编译和安装,初始化数据库并设置启动参数。2.3 启动与管理
启动数据库,登录并创建必要用户、数据库和表空间。3. 开机自动启动
3.1 init.d环境
使用start-scripts中的脚本配置init.d,确保PostgreSQL在系统启动时自动运行。3.2 systemd环境
为PostgreSQL创建systemd服务文件,确保启动和管理的自动化。4. psql操作示例
展示如何使用psql进行数据库操作,包括创建数据库、模式、表和数据插入等。5. 远程连接
讲解如何配置防火墙以允许远程连接。 通过以上步骤,您将掌握PostgreSQL 的驱动安装源码源码安装和基本管理,准备好进行数据管理和应用程序开发。干货 | PostgreSQL数据表文件底层结构布局分析
PostgreSQL提供了稳定、可靠的数据存储与管理功能,用户无需深究其底层原理,只需完成建库、建表、插入数据等基本操作,数据即可被持久化于PostgreSQL数据库中。然而,对于数据存储在磁盘上的位置、形式以及格式,我们充满好奇。本文旨在通过源码分析与实践操作,深入解析PostgreSQL底层数据的存储方式。
在PostgreSQL中,每个表由一个或多个堆文件表示,每个文件默认为1GB大小。当文件达到1GB后,PostgreSQL会创建新的堆文件。文件命名遵循特定规则:表OID加上序号ID(从1开始递增)。例如,一个名为student的表对应的堆文件名是。
数据库和表文件名由OID命名,定义在postgres_ext.h文件中。当数据被存储在PostgreSQL中时,用户插入的数据会依次存储在常规文件中,这些文件被称为堆文件。堆文件有四种类型:普通堆文件、临时堆文件、序列堆文件和TOAST表堆文件。其中,普通堆文件用于常规数据存储。
在研究表文件之前,需知晓PostgreSQL的数据目录位置,即PGDATA。通过psql命令SHOW DATA_DIRECTORY可以获取。对于关系型数据库,所有表都按数据库进行管理,即表属于特定数据库。
在没有数据时,文件大小为0字节。插入数据后,文件大小会增加至8KB。PostgreSQL在向表中插入数据时,以8KB为单位进行管理。文件大小达到1GB后,创建新文件,继续上述过程。
堆表文件由页组成,页的默认大小为8KB。每个页包含页头、行指针和堆元组。外贸erp 源码页头数据结构包括页的最后更改的LSN、校验和、位标志、空闲空间的开始和结束位置、特殊空间起始偏移量、页面大小及版本号、可删除的旧XID、行指针数组。
行指针数组用于索引元组,数组元素个数取决于页中元组的数量。元组结构分为普通数据元组和TOAST元组,用于存储变长数据。普通元组包含堆元组头部、位图和用户存储的数据。头部结构包含事务ID、事务ID、命令ID、元组表示符和元组状态信息。
为了查看堆表文件的详细信息,PostgreSQL提供扩展功能如pageinspect,可通过SQL命令创建使用。使用page_header()、get_raw_page()、heap_page_items()等函数可获取页头信息、元组头部信息和数据信息。
借助工具如hexdump和od,可以将二进制文件转换为十六进制编码,便于分析堆表文件的数据内容。通过分析页头、行指针和元组结构,可以深入了解PostgreSQL表文件的底层布局。
PostgreSQL内核Trigger的一生
本文简要介绍 PostgreSQL 数据库的 Trigger 从创建、存储、触发、执行、修改到删除的过程,贯穿 Trigger 的一生。
文中引用的函数、结构体来源于 PG 源码,分支为 REL__STABLE,对应的 commit id 如下。此外还引用了 PG 官方文档。
触发器简介
Trigger 即触发器,它可以在特定事件发生时,对数据库中的对象执行特定操作:
根据触发事件的不同,PG 的触发器分为两类:
不同数据库中触发器的分类有所不同,比如 Oracle 分为 DML Trigger 和 System Trigger,SQL Server 分为 DML Trigger、DDL Trigger 和 Login Trigger,不论其如何划分,多数都可以与 PG 的触发器对应上。
创建触发器语法
首先介绍创建触发器的 SQL 和 PLpgSQL 语法。
Trigger
根据 PG 官方文档,创建 Trigger 的语法如下:
下面以表 t1、t2 为例创建一个简单的触发器示例。表的定义如下:
触发器定义如下,是表 t1 上的行级触发器,对 t1 进行 INSERT 之后会触发,并执行 insert_into_t2 函数,将插入到 t1 的数据也插入到 t2。
insert_into_t2 函数定义如下,其中引用了上下文信息 NEW,表示插入到 t1 的数据,并将其插入到 t2。
Event Trigger
创建 Event Trigger 的语法如下,相比 Trigger 的语法要简单很多
以下是 PG 官方文档中的一个简单示例,该 Event Trigger 可以在任何 DDL 语句执行之前触发,并抛出异常,禁止执行任何 DDL 语句。
创建流程
简单介绍创建触发器时 PG 内核中的函数调用流程。
Trigger
CREATE TRIGGER 命令都属于 DDL 语句,所以会进入 DDL 的处理流程,关键的调用路径为: ProcessUtilitySlow-->CreateTrigger-->CreateTriggerFiringOn,CreateTriggerFiringOn 函数代码超过 行,因此只介绍其中的关键步骤:
Event Trigger
CREATE EVENT TRIGGER 的关键调用路径为: standard_ProcessUtility-->CreateEventTrigger,该函数流程相对简单很多:
触发器的存储
用户创建的触发器必须持久化到数据库中,具体的存储位置是触发器相关的系统表中。
Trigger
Trigger 存储在 pg_trigger 系统表中,表中的关键字段如下,包含触发器所在的表、触发器名、触发器调用的函数、是否可推迟等属性。总之,通过 CREATE TRIGGER 创建触发器时指定的任何信息都会存储到系统表中。
pg_trigger 系统表的各个字段在内存中用Trigger 结构体表示,定义如下,可见其成员变量与 pg_trigger 的属性是一一对应的。
在内存中的 relcache(表缓存)中也同样保存有 Trigger 的信息:
Event Trigger
Event Trigger 存储在 pg_event_trigger 系统表中,关键字段如下,包含触发器名、调用的函数等信息。与 Trigger 不同的是,这里并不包含触发器所在的表,因为 Event Trigger 不属于任何一个表。
触发过程
触发器会在特定事件场景下被触发,它识别这些事件的方式也很简单,就是在对应事件的代码处调用触发器函数。
Trigger
对于普通的触发器,触发时机是 INSERT、UPDATE、DELETE 等操作之前或者之后,所以在 PG 的执行器阶段触发,多数在 ProcessQuery-->ExecutorRun-->ExecModifyTable 函数中
我们将执行触发操作的函数称为“触发器的执行函数”,各类触发器的执行函数命名格式比较统一,在此列举几种:
以ExecBRInsertTriggers 为例说明触发过程:
Event Trigger
事件触发器支持的事件仅有 ddl_command_start、ddl_command_end、table_rewrite 和 sql_drop 这四类,分别对应四个执行函数,其触发时机说明如下:
以 EventTriggerDDLCommandStart 为例说明触发过程:
调用功能函数
用户在创建触发器的 EXECUTE { FUNCTION | PROCEDURE } function_name 语句中指定了该触发器要执行的功能函数。在触发器被触发后,会执行该函数。
Trigger
在执行器阶段触发时,ResultRelInfo 结构体中存有表上的各项信息,其中就包括表上的触发器、函数等,所以直接从中就可以拿到触发器信息。关键结构体为 ResultRelInfo、TriggerDesc、Trigger,其嵌套关系如下:
将ResultRelInfo 中获取的 Trigger 结构体的全部内容都填充到 TriggerData 结构体,ExecCallTriggerFunc 函数再从 TriggerData 中获取函数 oid,并执行该函数。
TriggerData 结构体定义如下,其中除了 Trigger 以外还保存了各种执行上下文信息,heap 表信息等,与函数的执行有关。
TriggerData 最终会保存到PLpgSQL_execstate 中,这是 PLpgSQL 执行过程中的一个重要结构体:
触发器的功能函数执行的方法与普通的 PLpgSQL 函数、存储过程执行方法是类似的,关键调用路径是: ExecCallTriggerFunc-->plpgsql_call_handler-->plpgsql_exec_trigger-->exec_toplevel_block-->exec_stmt_block-->…………
Event Trigger
对于事件触发器,在触发阶段的EventTriggerCommonSetup 函数中,通过 EventCacheLookup 从缓存中查找触发器功能函数,然后在 EventTriggerInvoke 中根据触发器函数的 oid 进行调用。
EventTriggerCommonSetup 中还会填充 EventTriggerData 结构体,其中保存了调用过程中的一些关键信息:
与普通触发器的 TriggerData 结构一样,EventTriggerData 结构体也会保存到PLpgSQL_execstate 中,在 PLpgSQL 执行过程中使用:
事件触发器的功能函数实际执行步骤与普通触发器也基本相同,关键调用路径为: ExecCallTriggerFunc-->plpgsql_call_handler-->plpgsql_exec_event_trigger-->exec_toplevel_block-->exec_stmt_block-->…………
修改触发器
使用 ALTER 语句修改触发器的定义
Trigger
根据 PG 官方文档,ALTER TRIGGER 的语法如下:
仅支持重命名和修改依赖的插件。
重命名触发器的关键调用流程为:standard_ProcessUtility-->ExecRenameStmt-->renametrig,基本原理也是读取 pg_trigger 系统表的信息,修改以后写回系统表。
修改触发器依赖插件的关键调用流程为:standard_ProcessUtility-->ExecAlterObjectDependsStmt,会修改 pg_depend 系统表。
Event Trigger
根据 PG 官方文档,ALTER EVENT TRIGGER 语法为:
支持对事件触发器进行重命名、禁用、启用、修改 owner 的操作。
ALTER TRIGGER 的关键函数是AlterEventTrigger,其内容较为简单:
删除触发器
使用 DROP 语句删除触发器
Trigger
PG 文档中 DROP TRIGGER 语法如下:
删除触发器的关键函数是RemoveTriggerById,调用流程如下: ProcessUtilitySlow-->ExecDropStmt-->RemoveObjects-->performMultipleDeletions-->deleteObjectsInList-->deleteOneObject-->doDeletion-->RemoveTriggerById
RemoveTriggerById 函数流程:
Event Trigger
PG 文档中 DROP EVENT TRIGGER 语法如下:
删除事件触发器的关键函数是DropObjectById,这是一个公用的函数,可以删除多种类型的对象。
调用流程如下: ProcessUtilitySlow-->ExecDropStmt-->RemoveObjects-->performMultipleDeletions-->deleteObjectsInList-->deleteOneObject-->doDeletion-->DropObjectById
PostgreSQL 技术内幕(十七):FDW 实现原理与源码解析
FDW,全称为Foreign Data Wrapper,是PostgreSQL提供的一种访问外部数据源的机制。它允许用户通过SQL语句访问和操作位于不同数据库系统或非数据库类数据源的外部数据,就像操作本地表一样。以下是从直播内容整理的关于FDW的使用详解、实现原理以及源码解析。 ### FDW使用详解 FDW在一定规模的系统中尤为重要,数据仓库往往需要访问外部数据来完成分析和计算。通过FDW,用户可以实现以下场景: 跨数据库查询:在PostgreSQL数据库中,用户可以直接请求和查询其他PostgreSQL实例,或访问MySQL、Oracle、DB2、SQL Server等主流数据库。 数据整合:从不同数据源整合数据,如REST API、文件系统、NoSQL数据库、流式系统等。 数据迁移:高效地将数据从旧系统迁移到新的PostgreSQL数据库中。 实时数据访问:访问外部实时更新的数据源。 PostgreSQL支持多种常见的FDW,能够直接访问包括远程PostgreSQL服务器、主流SQL数据库以及NoSQL数据库等多种外部数据源。### FDW实现原理
FDW的核心组件包括:1. **Foreign Data Wrapper (FDW)**:特定于各数据源的库,定义了如何建立与外部数据源的连接、执行查询及处理其他操作。例如,`postgres_fdw`用于连接其他PostgreSQL服务器,`mysql_fdw`专门连接MySQL数据库。
2. **Foreign Server**:本地PostgreSQL中定义的外部服务器对象,对应实际的远程或非本地数据存储实例。
3. **User Mapping**:为每个外部服务器设置的用户映射,明确哪些本地用户有权访问,并提供相应的认证信息。
4. **Foreign Table**:在本地数据库创建的表结构,作为外部数据源中表的映射。对这些外部表发起的SQL查询将被转换并传递给相应的FDW,在外部数据源上执行。
FDW的实现涉及PostgreSQL内核中的`FdwRoutine`结构体,它定义了外部数据操作的接口。接口函数包括扫描、修改、分析外部表等操作。### FDW源码解析
FDW支持多种数据类型,并以`Postgres_fdw`为例解析其源码。主要包括定义`FdwRoutine`、访问外部数据源、执行查询、插入、更新和删除操作的逻辑。 访问外部数据源:通过`postgresBeginForeignScan`阶段初始化并获取连接到远端数据源。 执行查询:进入`postgresIterateForeignScan`阶段,创建游标迭代器并从其中持续获取数据。 插入操作:通过`postgresBeginForeignInsert`、`postgresExecForeignInsert`和`postgresEndForeignInsert`阶段来执行插入操作。 更新/删除操作:遵循与插入操作相似的流程,包括`postgresBeginDirectModify`、`postgresIterateDirectModify`和相应的结束阶段。 对于更深入的技术细节,建议访问B站观看视频回放,以获取完整的FDW理解和应用指导。PostgreSQL源码学习笔记(6)-查询编译
查询模块是数据库与用户进行交互的模块,允许用户使用结构化查询语言(SQL)或其它高级语言在高层次上表达查询任务,并将用户的查询命令转化成数据库上的操作序列并执行。查询处理分为查询编译与查询执行两个阶段:
当PostgreSQL的后台进程Postgres接收到查询命令后,首先传递到查询分析模块,进行词法,语法与语义分析。用户的查询命令,如SELECT,CREATE TABLE等,会被构建为原始解析树,然后交给查询重写模块。查询重写模块根据解析树及参数执行解析分析及规则重写,得到查询树,最后输入计划模块得到计划树。
整个查询编译的函数调用流程包括查询分析、查询重写与计划生成三个阶段。查询分析涉及词法分析、语法分析与语义分析,分别由Lex与Yacc工具完成。词法分析识别输入的SQL命令中的模式,语法分析找出这些模式的组合,形成解析树。出于与用户交互的考虑,语义分析与重写放在另一个函数处理,以避免在输入语句时立即执行事务操作。Lex与Yacc是词法与语法分析工具,分别通过正则表达式解析与语法结构定义,生成用于分析的C语言代码。
查询分析由pg_parse_query函数与pg_analyze_and_rewrite函数完成。pg_parse_query处理词法与语法分析,而语义分析与重写在pg_analyze_and_rewrite函数中进行。语义分析需要访问数据库系统表,以检查命令中的表或字段是否存在,以及聚合函数的适用性。
查询重写核心在于规则系统,存储在pg_rewrite系统表中。规则系统由一系列重写规则组成,包括创建规则、删除规则以及利用规则进行查询重写三个操作。规则系统提供定义、删除规则以及利用规则优化查询的功能。PG中实现多种查询优化策略,包括谓语下滑、WHERE语句合并等,通过动态规划与遗传算法选择代价最小的执行方案。
查询规划的总体过程包括预处理、生成路径和生成计划三个阶段。预处理阶段消除冗余条件、减少递归层数与简化路径生成。提升子链接与子查询是预处理中的关键步骤,通过将子查询提升至与父查询相同的优化等级,提高查询效率。提升子链接与子查询的函数包括pull_up_sublinks与pull_up_subqueries。
在路径生成阶段,优化器检查MIN/MAX聚集函数的存在与索引条件,生成通过索引扫描获得最大值或最小值的路径。表达式预处理由preprocess_expression函数完成,包括目标链表、WHERE语句、HAVING谓语等的处理。HAVING子句的提升或保留取决于是否包含聚集条件。删除冗余信息以优化路径生成。
生成路径的入口函数query_planner负责找到从一组基本表到最终连接表的最高效路径。路径生成算法包括动态规划与遗传算法,分别解决路径选择与状态传递问题。路径生成流程涉及make_one_rel函数,最终生成最优路径并转换为执行计划。
在得到最优路径后,优化器根据路径生成对应的执行计划。创建计划的入口函数create_plan提供顺序扫描、采样扫描、索引扫描与TID扫描等计划生成。整理计划树函数set_plan_references负责最后的细节调整,优化执行器执行效率。代价估算考虑磁盘I/O与CPU时间,根据统计信息与查询条件估计路径代价。
查询编译与规划是数据库性能的关键环节。PostgreSQL通过高效的查询分析、重写与规划,生成最优执行计划,显著提高查询执行效率。动态规划与遗传算法等优化策略的应用,确保了查询处理的高效与灵活性。