数据库集群、数据库和表
数据库集群的逻辑结构
数据库集群是由 PostgreSQL 服务器管理 的数据库集合。 PostgreSQL 中的术语“数据库集群”并不意味着“一组数据库服务器”。PostgreSQL 服务器在单个主机上运行并管理单个数据库集群。
数据库是数据库对象的集合。在关系数据库理论中,数据库对象是一种用于存储或引用数据的数据结构。(堆)表就是它的一个典型例子,还有很多像索引、序列、视图、函数等。在 PostgreSQL 中,数据库本身也是数据库对象,并且在逻辑上是相互分离的。所有其他数据库对象(例如,表、索引等)都属于它们各自的数据库。
数据库集群的逻辑结构PostgreSQL 中的所有数据库对象都由各自的对象标识符 (OID)进行内部管理,它们是无符号的 4 字节整数。根据对象的类型,数据库对象和相应 OID 之间的关系存储在适当的系统目录中。例如,数据库和堆表的 OID 分别存储在pg_database和pg_class中。
openGauss=# select datname, oid from pg_database where datname='test';
datname | oid
---------+-------
test | 16386
(1 row)
数据库集群的物理结构
数据库集群基本上是 一个称为基本目录的目录,它包含一些子目录和大量文件。如果您执行initdb实用程序来初始化新的数据库集群,则会在指定目录下创建一个基本目录。虽然不是强制性的,但基本目录的路径通常设置为环境变量PGDATA。
下图显示了 PostgreSQL 中的数据库集群示例。数据库是基本子目录下的子目录,每个表和索引都是(至少)一个存储在其所属数据库的子目录下的文件。还有几个包含特定数据和配置文件的子目录。虽然 PostgreSQL 支持tablespaces,但该术语的含义与其他 RDBMS 不同。PostgreSQL 中的表空间是一个包含基本目录之外的一些数据的目录。
数据库集群的一个例子数据库集群布局
根目录文件
文件 | 描述 |
---|---|
PG_VERSION | 包含 PostgreSQL 主要版本号的文件 |
pg_hba.conf | 用于控制 PosgreSQL 客户端身份验证的文件 |
pg_ident.conf | 一个控制 PostgreSQL 用户名映射的文件 |
postgresql.conf | 用于设置配置参数的文件 |
postgresql.auto.conf | 用于存储在 ALTER SYSTEM(版本 9.4 或更高版本)中设置的配置参数的文件 |
postmaster.opts | 记录服务器上次启动时使用的命令行选项的文件 |
子目录
子目录 | 描述 |
---|---|
base/ | 包含每个数据库子目录的子目录。 |
global/ | 包含集群范围表的子目录,例如 pg_database 和 pg_control。 |
pg_commit_ts/ | 包含事务提交时间戳数据的子目录。版本 9.5 或更高版本。 |
pg_clog/(9.6 或更早版本) | 包含事务提交状态数据的子目录。在版本 10 中它被重命名为pg_xact |
pg_dynshmem/ | 包含动态共享内存子系统使用的文件的子目录。版本 9.4 或更高版本。 |
pg_logical/ | 包含用于逻辑解码的状态数据的子目录。版本 9.4 或更高版本。 |
pg_multixact/ | 包含多事务状态数据的子目录(用于共享行锁) |
pg_notify/ | 包含 LISTEN/NOTIFY 状态数据的子目录 |
pg_repslot/ | 包含复制槽 数据的子目录。版本 9.4 或更高版本。 |
pg_serial/ | 包含有关提交的可序列化事务信息的子目录(9.1 或更高版本) |
pg_snapshots/ | 包含导出快照的子目录(版本 9.2 或更高版本)。PostgreSQL 的函数 pg_export_snapshot 在这个子目录中创建一个快照信息文件。 |
pg_stat/ | 包含统计子系统的永久文件的子目录。 |
pg_stat_tmp/ | 包含统计子系统的临时文件的子目录。 |
pg_subtrans/ | 包含子事务状态数据的子目录 |
pg_tblspc/ | 包含指向表空间的符号链接的子目录 |
pg_twophase/ | 包含准备交易的状态文件的子目录 |
pg_wal/(版本 10 或更高版本) | 包含 WAL(预写日志记录)段文件的子目录。它是从版本 10 中的pg_xlog重命名的。 |
pg_xact/(版本 10 或更高版本) | 包含事务提交状态数据的子目录。它是从版本 10 中的 pg_clog 重命名的。 |
pg_xlog/(9.6 或更早版本) | 包含 WAL(预写日志记录)段文件的子目录。在版本 10 中它被重命名为pg_wal 。 |
数据库布局
数据库是base子目录下的子目录;并且数据库目录名称与各自的 OID 相同。
与表和索引相关的文件布局
每个大小小于 1GB 的表或索引都是存储在其所属数据库目录下的单个文件。作为数据库对象的表和索引在内部由各个 OID 管理,而这些数据文件由变量relfilenode管理。表和索引的 relfilenode 值基本但并不总是与各自的 OID 匹配.
test=# SELECT relname, oid, relfilenode FROM pg_class WHERE relname = 'students';
relname | oid | relfilenode
----------+-------+-------------
students | 16393 | 16393
(1 row)
对应目录:
[root@localhost base]# ll ./16386/16393
-rw------- 1 omm omm 8192 May 19 02:33 ./16386/16393
也可以使用(pg9.0以上版本,opengauss都支持):
test=# select pg_relation_filepath('students');
pg_relation_filepath
----------------------
base/16386/16393
-
可以通过发出一些命令(例如,TRUNCATE、REINDEX、CLUSTER)来更改表和索引的 relfilenode 值。
-
当表和索引的文件大小超过 1GB 时,PostgreSQL 会创建一个名为 relfilenode.1 的新文件并使用它。如果新文件已填满,将创建下一个名为 relfilenode.2 的新文件,依此类推。
-
仔细查看数据库子目录,你会发现每个表都有两个关联文件,分别以'_fsm'和'_vm'为后缀。这些被称为空闲空间映射和可见性映射,分别存储空闲空间容量和表格文件中每个页面的可见性信息(参见第 5.3.4节和第 6.2节中的更多详细信息)。索引只有单独的可用空间图,没有可见性图。它们也可以在内部称为每个关系的分支;可用空间映射是表/索引数据文件的第一个分支(分支编号为 1),可见性映射是表数据文件的第二个分支(分支编号为 2)。数据文件的fork号为0。
表空间
PostgreSQL 中的表空间是基本目录之外的附加数据区域。下图显示了一个表空间的内部布局,以及与主数据区的关系。
数据库集群中的一个表空间堆表文件的内部布局
在数据文件(堆表和索引,以及空闲空间映射和可见性映射)内部,分为固定长度的页(或块),默认为 8192 字节(8 KB)。每个文件中的那些页面从 0 开始依次编号,这些编号称为块编号。如果文件已被填满,PostgreSQL 会在文件末尾添加一个新的空页以增加文件大小。
堆表文件的页面布局表中的一个页面包含如下描述的三种数据:
-
heap tuple(s) ——堆元组本身就是一个记录数据。它们从页面底部开始按顺序堆叠。tuple 的内部结构此处暂不讨论。
-
line pointer(s) – 一个行指针有 4 个字节长,并保存一个指向每个堆元组的指针。它也称为item pointer。
行指针组成一个简单的数组,起到元组索引的作用。每个索引从 1 开始按顺序编号,称为偏移编号。当一个新的元组被添加到页面时,一个新的行指针也被推到数组上以指向新的。 -
标头数据——由结构[PageHeaderData](javascript:void(0))定义的标头数据分配在页面的开头。它长 24 字节,包含有关页面的一般信息。结构的主要变量如下所述。
- pd_lsn——这个变量存储了本页最后一次更改写入的 XLOG 记录的 LSN。它是一个 8 字节的无符号整数,与 WAL(Write-Ahead Logging)机制有关。
- pd_checksum – 此变量存储此页面的校验和值。(注意9.3及以后版本支持这个变量;在早期版本中,这部分已经存储了页面的timelineId。)
- pd_lower, pd_upper – pd_lower 指向行尾指针,pd_upper 指向最新堆元组的开头。
- pd_special – 此变量用于索引。在表中的页面中,它指向页面的末尾。(在索引内的页面中,它指向特殊空间的开头,这是仅由索引持有的数据区域,根据索引类型的种类,如B-tree、GiST、GiN等包含特定的数据。)
行尾指针和最新元组开头之间的空白空间称为空闲空间或空洞。
为了识别表中的元组,内部使用了元组标识符 (TID)。TID 包含一对值:包含元组的页面的块号,以及指向元组的行指针的偏移量。其用法的一个典型例子是索引。
元组的读写方法
编写堆元组
假设一个表由一页组成,其中只包含一个堆元组。该页的 pd_lower 指向第一行指针,行指针和 pd_upper 都指向第一个堆元组。见图 1.5(a)。
当第二个元组被插入时,它被放在第一个元组之后。第二行指针被推到第一个上,它指向第二个元组。pd_lower 更改为指向第二行指针,pd_upper 更改为指向第二个堆元组。见图 1.5(b)。此页面中的其他标题数据(例如,pd_lsn、pg_checksum、pg_flag)也被重写为适当的值。
堆元组的编写读取堆元组
这里概述了两种典型的访问方法,顺序扫描和 B-tree 索引扫描:
- 顺序扫描——通过扫描每页中的所有行指针顺序读取所有页中的所有元组。见图 1.6(a)。
- B-tree 索引扫描 ——一个索引文件包含索引元组,每个索引元组由一个索引键和一个指向目标堆元组的 TID 组成。如果找到了具有您要查找的键的索引元组,则 PostgreSQL 使用获得的 TID 值读取所需的堆元组。(B-tree index中查找索引元组的方法,由于很常见,篇幅有限,这里就不做说明了,见相关资料。)例如,图1.6(b)中,TID得到的索引元组的值为'(block = 7, Offset = 2)'。这意味着目标堆元组是表内第 7 页中的第 2 个元组,因此 PostgreSQL 可以读取所需的堆元组,而无需在页面中进行不必要的扫描。