合 《PostgreSQL技术内幕——原理探索》第一章 数据库集簇,数据库,数据表
Tags: PGPostgreSQL翻译《PostgreSQL技术内幕——原理探索》简介
第一章和第二章简单介绍了一些PostgreSQL的基础知识,有助于读者理解后续章节的内容。本章包括以下几个主题:
- 数据库集簇(database cluster)的逻辑结构
- 数据库集簇的物理结构
- 堆表(heap table)文件的内部布局
- 从表中读写数据的方式
如果你已经熟悉这些内容,可以跳过本章。
1.1 数据库集簇的逻辑结构
数据库集簇(database cluster)是一组数据库(database) 的集合,由一个PostgreSQL服务器管理。第一次听到这个定义也许会令人疑惑,PostgreSQL中的术语“数据库集簇”,并非 意味着“一组数据库服务器”。 一个PostgreSQL服务器只会在单机上运行并管理单个数据库集簇。
图1.1展示了一个数据库集簇的逻辑结构。 数据库(database)是数据库对象(database objects)的集合。 在关系型数据库理论中,数据库对象是用于存储或引用数据的数据结构。 (堆)表是一个典型的例子,还有更多种对象,例如索引,序列,视图,函数等。 在PostgreSQL中数据库本身也是数据库对象,并在逻辑上彼此分离。 所有其他的数据库对象(例如表,索引等)归属于各自相应的数据库。
图1.1 数据库集簇的逻辑结构
在PostgreSQL内部,所有的数据库对象都通过相应的对象标识符(Object Identifiers, OID)进行管理,这些标识符是无符号的4字节整型。数据库对象与相应OID之间的关系存储在相应的系统目录中,依具体的对象类型而异。 例如数据库和堆表对象的OID分别存储在pg_database
和pg_class
中,因此当你希望找出OID时,可以执行以下查询:
1 2 3 4 5 6 7 8 9 10 11 | sampledb=# SELECT datname, oid FROM pg_database WHERE datname = 'sampledb'; datname | oid ----------+------- sampledb | 16384 (1 row) sampledb=# SELECT relname, oid FROM pg_class WHERE relname = 'sampletbl'; relname | oid -----------+------- sampletbl | 18740 (1 row) |
1.2 数据库集簇的物理结构
数据库集簇在本质上就是一个文件目录,名曰基础目录(base directory),包含着一系列子目录与文件。 执行 initdb
命令会在指定目录下创建基础目录从而初始化一个新的数据库集簇。 通常会将基础目录的路径配置到环境变量PGDATA
中,但这并不是必须的。
图1.2 展示了一个PostgreSQL数据库集簇的例子。 base
子目录中的每一个子目录都对应一个数据库,数据库中每个表和索引都会在相应子目录下存储为(至少)一个文件;还有几个包含特定数据的子目录,以及配置文件。 虽然PostgreSQL支持表空间(Tablespace),但该术语的含义与其他RDBMS不同。 PostgreSQL中的表空间对应一个包含基础目录之外数据的目录。
图1.2 数据库集簇示例
后续小节将描述数据库集簇的布局,数据库的布局,表和索引对应的文件布局,以及PostgreSQL中表空间的布局。
1.2.1 数据库集簇的布局
官方文档中描述了数据库集簇的布局。 表1.1中列出了主要的文件与子目录:
表 1.1 基本目录下的数据库文件和子目录的布局(参考官方文档)
文件 | 描述 |
---|---|
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-) | 事务提交状态数据(9.6及更老版本),在版本10中被重命名为pg_xact 。CLOG将在5.4节中描述 |
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/ | 两阶段事务(prepared transactions)的状态文件 |
pg_wal/ (10+) | WAL( Write Ahead Logging)段文件(10或更新版本),从pg_xlog 重命名而来。 |
pg_xact/ (10+) | 事务提交状态数据,(10或更新版本),从pg_clog 重命名而来。CLOG将在5.4节中描述。 |
pg_xlog/ (9.6-) | WAL(Write Ahead Logging)段文件(9.6及更老版本),它在版本10中被重命名为pg_wal 。 |
1.2.2 数据库布局
一个数据库与base
子目录下的一个子目录对应;且该子目录的名称与相应数据库的OID相同。 例如当数据库sampledb
的OID为16384时,它对应的子目录名称即为16384。
1 2 3 | $ cd $PGDATA $ ls -ld base/16384 drwx------ 213 postgres postgres 7242 8 26 16:33 16384 |
1.2.3 表与索引相关文件的布局
每个小于1GB的表或索引都在相应的数据库目录中存储为单个文件。在数据库内部,表和索引作为数据库对象是通过OID来管理的,而这些数据文件则由变量relfilenode
管理。 表和索引的relfilenode
值通常与其OID一致,但也有例外,下面将详细展开。
让我们看一看表sampletbl
的oid
和relfilenode
:
1 2 3 4 5 | sampledb=# SELECT relname, oid, relfilenode FROM pg_class WHERE relname = 'sampletbl'; relname | oid | relfilenode -----------+-------+------------- sampletbl | 18740 | 18740 (1 row) |
从上面的结果可以看出oid
和relfilenode
值相等。还可以看到表sampletbl
的数据文件路径是base/16384/18740
。
1 2 3 | $ cd $PGDATA $ ls -la base/16384/18740 -rw------- 1 postgres postgres 8192 Apr 21 10:21 base/16384/18740 |
表和索引的relfilenode
值会被一些命令(例如TRUNCATE
,REINDEX
,CLUSTER
)所改变。 例如对表 sampletbl
执行TRUNCATE
,PostgreSQL会为表分配一个新的relfilenode
(18812),删除旧的数据文件(18740),并创建一个新的数据文件(18812)。
1 2 3 4 5 6 7 8 | sampledb=# TRUNCATE sampletbl; TRUNCATE TABLE sampledb=# SELECT relname, oid, relfilenode FROM pg_class WHERE relname = 'sampletbl'; relname | oid | relfilenode -----------+-------+------------- sampletbl | 18740 | 18812 (1 row) |
在9.0或更高版本中,内建函数
pg_relation_filepath
能够根据OID或名称返回关系对应的文件路径,非常实用。
12345 sampledb=# SELECT pg_relation_filepath('sampletbl');pg_relation_filepath----------------------base/16384/18812(1 row)
当表和索引的文件大小超过1GB时,PostgreSQL会创建并使用一个名为relfilenode.1
的新文件。如果新文件也填满了,则会创建下一个名为relfilenode.2
的新文件,依此类推。
译者注:数据库系统中的表(Table)与关系代数中的关系(Relation)关系紧密但又不尽相同。在PostgreSQL中,表,索引,TOAST表都归类为关系。
1 2 3 4 5 | $ cd $PGDATA $ ls -la -h base/16384/19427* -rw------- 1 postgres postgres 1.0G Apr 21 11:16 data/base/16384/19427 -rw------- 1 postgres postgres 45M Apr 21 11:20 data/base/16384/19427.1 ... |
在构建PostgreSQL时,可以使用配置选项
--with-segsize
更改表和索引的最大文件大小。
仔细观察数据库子目录就会发现,每个表都有两个与之相关联的文件,后缀分别为_fsm
和_vm
。这些实际上是空闲空间映射(free space map)和可见性映射(visibility map) 文件,分别存储了表文件每个页面上的空闲空间信息与可见性信息(更多细节见第5.3.4节和第6.2节)。索引没有可见性映射文件,只有空闲空间映射文件。
一个具体的示例如下所示:
1 2 3 4 5 | $ cd $PGDATA $ ls -la base/16384/18751* -rw------- 1 postgres postgres 8192 Apr 21 10:21 base/16384/18751 -rw------- 1 postgres postgres 24576 Apr 21 10:18 base/16384/18751_fsm -rw------- 1 postgres postgres 8192 Apr 21 10:18 base/16384/18751_vm |
在数据库系统内部,这些文件(主体数据文件,空闲空间映射文件,可见性映射文件等)也被称为相应关系的分支(fork);空闲空间映射是表/索引数据文件的第一个分支(分支编号为1),可见性映射表是数据文件的第二个分支(分支编号为2),数据文件的分支编号为0。
译者注:每个 关系(relation) 可能会有四种分支,分支编号分别为0,1,2,3,0号分支
main
为关系数据文件本体,1号分支fsm
保存了main
分支中空闲空间的信息,2号分支vm
保存了main
分支中可见性的信息,3号分支init
是很少见的特殊分支,通常用于不被日志记录(unlogged)的表与索引。每个分支都会被存储为磁盘上的一到多个文件:PostgreSQL会将过大的分支文件切分为若干个段,以免文件的尺寸超过某些特定文件系统允许的大小,也便于一些归档工具进行并发复制,默认的段大小为1GB。
1.2.4 表空间
PostgreSQL中的表空间(Tablespace)是基础目录之外的附加数据区域。 在8.0版本中引入了该功能。