PostgreSQL内幕探索—基础知识

发布于:2025-04-14 ⋅ 阅读:(18) ⋅ 点赞:(0)

PostgreSQL内幕探索—基础知识

PostgreSQL(以下简称PG) 起源于 1986 年加州大学伯克利分校的 ‌POSTGRES 项目‌,最初以对象关系模型为核心,支持高级数据类型和复杂查询功能‌。

1996 年更名为 PostgreSQL 并开源,逐步发展为功能全面的企业级关系型数据库系统,支持 SQL 标准并持续扩展特性(如多版本并发控制、地理空间数据支持等)‌

PG基础知识

大致从下面几个方面来了解PG的基础知识

  • 数据集簇
  • 数据库
  • 表和索引
  • 表空间
  • 表文件
  • 元组

数据集簇

数据集簇是一组数据库的集合,由一个PG服务器进行管理,可以简单理解为一个数据库实例。我们通过一张图来了解一个数据集簇的逻辑结构,如下图所示

逻辑结构

图1.1

从图中我们可以知道,一个数据集簇中可以有多个数据库,每个数据库中包含了不同的对象,例如表table、索引index、视图view等。每个对象都有唯一的一个标识,称之为对象标识符oid,PG通过oid来管理各个对象。数据库中对象与oid的映射关系存储在对应的系统目录中,因对象不同有所差异,例如数据库对象存在于pg_database下,而堆表对象存在于pg_class下。如果需要查找对象的oid,可以使用下面的语句

# 查询数据库对象的oid
select datname,oid from pg_database where datname = 'testdb';

# 查询表对象的oid
select relname,oid from pg_class where relname = 't1';
物理结构

上面了解了数据集簇的逻辑结构,我们现在来认识数据集簇的物理结构。数据集簇的物理结构本质上是一个文件目录,包含了很多子目录和文件,通过initdb可以在指定目录$PGDATA下创建一个初始化的数据集簇。下面的重要目录包含了base目录,默认有几个模板数据库,可以直接在目录下直接看到他们的oid。新创建的数据库都会在这个目录下面

数据集簇布局

这里直接摘用文章中的内容来详细展示数据集簇的布局,列出主要的文件和子目录

  • 文件
文件 描述
PG_VERSION 包含PG主版本号的文件
pg_hba.conf 控制PG的客户端认证
pg_ident.conf 控制PG的用户名映射
postgresql.conf 参数配置文件
postgresql.auto.conf 存储使用alter system命令修改的配置参数
postmaster.opts 记录上次启动的命令行选项
  • 子目录
子目录 描述
base/ 每个数据库的子目录皆存储在此
global/ 数据集簇范围的表以及pg_control文件
pg_commit_ts/ 事务提交时间戳(9.5+)
pg_clog/ 事务提交状态日志
pg_dynshmem/ 动态共享内存子系统使用的文件
pg_logical/ 逻辑解码的状态数据
pg_mulixact/ 多事务状态数据
pg_notify/ LISTEN/NOTIFY数据
pg_repslot/ 复制槽数据(9.4+)
pg_serial/ 已提交的可串行化事务数据
pg_snapshots/ 存储导出的快照信息
pg_stat/ 统计子系统的永久文件
pg_stat_temp/ 统计子系统的临时文件
pg_subtrans/ 子事务状态数据
pg_tblspc/ 指向表空间的符号连接
pg_twophase/ 两阶段事务的状态文件
pg_xlog/ wal日志文件
数据库布局

在base子目录下有对应的oid目录,例如testdb的oid为16384,那么目录为base/16384

表和索引文件布局

每个小于1GB的表或者索引都在相应的数据库目录下存在对应的文件。数据库内部,表和索引是用oid进行管理的,但是在文件上有所差异,文件中使用的是refilenode,这个refilenode一般和oid相同,但是如果表发生了truncate、reindex、cluster等命令被改变,那么这个refilenode就会发生改变。
使用内置函数pg_relation_filepath可以轻松获取到表或索引对象的文件路径,当表或索引对象的数据超过了那么1GB之后,那么PG会创建一个refilenode.1的新文件,如果新文件再次被用满,生成refilenode.2文件,依此类推
在初始化PG时,可以通过--with-segsize来指定表和索引文件的最大文件大小。

_fsm文件和_vm文件

每个表都有相关联的_fsm(空闲空间映射)文件和_vm(可见性映射)文件,分别存储了表文件上每个页面的空闲空间信息和可见性信息。索引只有_fsm文件

内部相关概念

在数据库内部,这些文件被称为相应的分支。一般有四种分支

  • 0 数据文件本体
  • 1 fsm保存空闲空间信息
  • 2 vm保存可见性信息
  • 3 不常见的特殊分支,表示不被日志记录的表和索引
表空间布局

表空间是在基础目录下附加的目录,在建表时指定表空间,那么会将此表的文件生成到该表空间的目录下,该目录下还会创建一个PG_主版本号_目录版本号的目录。通过pg_tblspc子目录进行关联

堆表文件内部布局

数据文件(堆表,索引,也包括vm和fsm文件)内部被划分为固定长度的页,或者叫区块,大小默认为8KB,可以在PG初始化的时候进行调整。每个文件中的页从0开始编号,称之为区块号。如果当前页被填满,那么在末尾追加一个新页来扩展文件大小。
堆表文件的布局如下图
在这里插入图片描述

下面来详细解释下表文件一个页(区块)中的三种数据类型

  1. 堆元组tuple:这里的tuple可以理解为行,存储的就是数据本身,从页面底部开始堆叠
  2. 行指针:保存者指向堆元组的指针,每个行指针占4B,形成一个简单的数组,扮演了元组索引的角色。每个索引项从1开始编号,这个编号称为偏移量。每次有新元组插入时,同时也会插入行指针,并指向新元组。
  3. 头部数据header:header中包含了很多重要的信息,下面一一列出
    • pd_lsn 记录本页面最近一次变更写入xlog记录的lsn号,和wal机制相关
    • pd_checksum 记录本页面的校验和值
    • pd_lower、pd_upper pd_lower指向行指针的末尾,pd_upper指向最新元组的起始位置,可以使用pd_upper-pd_lower得到当前页大致的剩余可用空间
    • pd_special 在索引页中会用到,在堆表页指向页尾。在索引中指向特殊区域的起始位置,特殊区域是仅由索引使用的特殊数据区域,例如btree,空间索引等
    • pd_prune_xid 本页面中可以修剪的最老元组中的xid
      为了标识表中的元组,数据库内部会使用元组标识符tid来管理。tid由一对值组成,分别是元组所在区块号和行指针数组的偏移量。
      此外,大小如果超过了2KB(1/4)的堆元组会使用一种TOAST的方法来存储和管理

读写元组的方式

下面详细介绍下读写元组的流程,假设目前有一张表,这张表只有一个数据页,且页中只有一个tuple。

写元组

根据列子的情况,我们画出写入元组前后的对比
在这里插入图片描述

我们大致描述一下整体的步骤

  1. 从页面底部插入一条tuple,放在tuple1之后,将pd_upper的指针指向tuple2的起始位置
  2. 在行指针1后面生成一个行指针,偏移量为2,行指针2指向tuple2,pd_lower指向行指针2的末尾
  3. pd_lsn,pg_checksum,pg_flag也被修改为合适的值,后面详细展开说。
读元组

我们这里介绍两种常见的读取方式

  • 顺序扫描:通过扫描每一页的行指针,依次读取所有页面的所有元组。
  • btree索引扫描:索引文件中包含了索引元组,索引元组由一对值组成,key为索引列的值,value为目标元组的tid。这样可以直接通过索引读取到目标元组,避免不必要的页面扫描。