数据库集簇指由单个 PostgreSQL 服务器实例管理的数据库集合,组成数据库集簇的这些数据库使用相同的全局配置文件和监听端口 、共用进程和内存结构。
某一个数据库实例通常是指某个数据库集簇
数据库集簇是数据库对象的集合。
数据库本身也是数据库对象 , 一个数据库集簇可以包含多个 Database 、多个 User ,每个 Database 以及 Database 中的所有对象都有它们的所有者 : User。
创 建一 个 Database 时 会 为这个 Databas巳创 建一 个 名为 public 的 默 认 Schema ,每个 Database 可以 有多个 Schema ,在这个数据库中创建其他数据库对象时如 果没有指定Schema , 都会在 public 这个 Schema 中。Schema 可 以 理解为一个数据库 中的命名空 间 ,在数据库 中创建的所有对象都在 Schema 中创建 一个用户可以从 同一个客户端连接中访问不 同的 Schema。 不同的 Schema 中可以有多个相同名称的 Table 、 Index 、 View 、 S equence、Function 等数据库对象 。
数据库的文件默认保存在 initdb 时创建的数据目 录 中,数据文件之外 还有参数文件 、控制文件、数据库运行 日 志及预写 日志等 。
数据目录用来存放 PostgreSQL 持久化的数据 , 通常可以将数据目录路径配置为 PGDATA环境变量,查看数据目 录有哪些子目录和文件的命令如下所示 :
[postgres@localhost ~]$ tree -L 1 $PGDATA
/usr/local/pgsql/data
├── base
├── global
├── pg_commit_ts
├── pg_dynshmem
├── pg_hba.conf
├── pg_ident.conf
├── pg_logical
├── pg_multixact
├── pg_notify
├── pg_replslot
├── pg_serial
├── pg_snapshots
├── pg_stat
├── pg_stat_tmp
├── pg_subtrans
├── pg_tblspc
├── pg_twophase
├── PG_VERSION
├── pg_wal
├── pg_xact
├── postgresql.auto.conf
├── postgresql.conf
├── postmaster.opts
└── postmaster.pid
base 子目录是数据文件默认保存的位置,是数据库初始化后的默认表空间
基础的数据库对象 : OID 和表空间
所有数据库对象都由各自的对象标识符( OID )进行内部管理,它们是
无符号的 4 字节整数 。 数据库对象和各个 OID 之间的关系存储在适当的系统目录中,具体取决于对象的类型 。 数据库的 OID 存储在 pg_database 系统表中,可以通过如下代码查询数据库的 OID:
postgres=# select oid,datname from pg_database where datname='postgres';
oid | datname
-------+----------
13892 | postgres
(1 row)
数据库 中的表、索引 、序列等对象的 OID 存储在 pg_class 系统表中,可以通过如下代码查询获得这些对象的 OID:
postgres=# select oid,relname,relkind from pg_class where relname='tb1_user_json';
oid | relname | relkind
-------+---------------+---------
24594 | tb1_user_json | r
最大的逻辑存储单位是表空间,数据库中创建的对象都保存在表空间
中 ,例如表、索引和整个数据库都可以被分配到特定的表空间 。 在创建数据库对象时,可以指定数据库对象的表空间,如果不指定则使用默认表空间,也就是数据库对象的文件的位置。 初始化数据库目录时会自动创建 pg_default 和 pg_global 两个表空间 。
postgres=# \db
List of tablespaces
Name | Owner | Location
------------+----------+----------
pg_default | postgres |
pg_global | postgres |
(2 rows)
除 了两个默认表空 间 , 用户还可以创建自定义表空间 。 使用自定义表空 间有两个典型的场景 :
由于现在固态存储已经很普遍,这种文件布局方式反倒会增加维护成本 。
要创建一个表空间,先用操作系统的 postgres 用户创建一个目录,然后连接到数据库,使用 CREATE TABLESPACE 命令创建表空间,如下所示:
[postgres@localhost data]$ mkdir -p /usr/local/pgsql/data/mytblspc
[postgres@localhost data]$ psql
psql (14.5)
Type "help" for help.
postgres=# create tablespace myspc location '/usr/local/pgsql/data/mytblspc';
WARNING: tablespace location should not be inside the data directory
CREATE TABLESPACE
postgres=# \db
List of tablespaces
Name | Owner | Location
------------+----------+--------------------------------
myspc | postgres | /usr/local/pgsql/data/mytblspc
pg_default | postgres |
pg_global | postgres |
(3 rows)
创建新的数据库或表时,可以指定创建的表空 间
create table t(id serial primary key,ival int) tablespace myspc;
由于表空间定义了存储的位置 ,在创建数据库对象时,会在当前的表空 间 目录创建一个以数据库 OID 命名的目录,该数据库的所有对象将保存在这个目录中,除非单独指定表空 间 。
postgres=# select oid,datname from pg_database where datname='postgres';
oid | datname
-------+----------
13892 | postgres
(1 row)
[postgres@localhost data]$ ll $PGDATA/base/13892
total 1723404
-rw-------. 1 postgres postgres 8192 Oct 21 22:35 112
-rw-------. 1 postgres postgres 8192 Oct 21 22:35 113
-rw-------. 1 postgres postgres 122880 Oct 24 20:18 1247
-rw-------. 1 postgres postgres 24576 Oct 21 23:32 1247_fsm
[postgres@localhost data]$ ll $PGDATA/mytblspc/PG_14_202107181/13892
total 0
-rw-------. 1 postgres postgres 0 Oct 24 20:16 24639
通过以上查询可知 postgres 的 OID 为13892 ,postgres 的表、索引都会保存
在 $PGDATA/base/13892 这个目录中
在数据库中创建对象,例如表、索引时首先会为表和索引分配段 。 在 PostgreSQL 中,每个表和索引都用一个文件存储,新创建的表文件以表的 OID 命名 , 对于大小超出 l GB 的表数据文件, PostgreSQL 会自动将其切分为多个文件来存储,切分出的文件用 OID.<顺序号〉来命名 。 但表文件并不是总是“ OID .< 顺序号 > ”命名 ,实际上真正管理表文件的是pg_class 表中的 relfilenode 字段的值,在新创建对象时会在 pg_class 系统表中插入该表的记录,默认会以 OID 作为 relfilenode 的值,但经过几次 VACUUM 、 TRUNCATE 操作之后,relfilenode 的值会发生变化 。
postgres=# select oid,relfilenode from pg_class where relname='t';
oid | relfilenode
-------+-------------
24639 | 24639
(1 row)
postgres=# \! ls -l /usr/local/pgsql/data/base/13892/24639*
-rw-------. 1 postgres postgres 8192 Oct 23 00:31 /usr/local/pgsql/data/base/13892/24639
默认情况下 , tbl 表的 OID 为 24639 , relfilenode 也是 24639 ,表 的物理文件为“/usr/local/pgsql/data/base/13892/24639 ” 。 依次 TRUNCATE 清空 t 表的所有数据
postgres=# create table test(id int4 primary key);
CREATE TABLE
postgres=# select oid,relfilenode from pg_class where relname='test';
oid | relfilenode
-------+-------------
24647 | 24647
(1 row)
postgres=# \! ls -l /usr/local/pgsql/data/base/13892/24647*
-rw-------. 1 postgres postgres 0 Oct 24 21:19 /usr/local/pgsql/data/base/13892/24647
postgres=# truncate table test;
TRUNCATE TABLE
postgres=# checkpoint;
CHECKPOINT
postgres=# \! ls -l /usr/local/pgsql/data/base/13892/24647*
ls: cannot access /usr/local/pgsql/data/base/13892/24647*: No such file or directory
postgres=# select oid,relfilenode from pg_class where relname='test';
oid | relfilenode
-------+-------------
24647 | 24652
(1 row)
postgres=# \! ls -l /usr/local/pgsql/data/base/13892/24652*
-rw-------. 1 postgres postgres 0 Oct 24 21:20 /usr/local/pgsql/data/base/13892/24652
在 默认情况下 表的物理文件为“ /usr/local/pgsql/data/base/13892/24647 ” 。 依次 TRUNCATE 清空表的所有数据后它的命名规则为 <relfilenode >.<顺序号>/usr/local/pgsql/data/base/13892/24652
CREATE TABLE tbl(ival int4,description text,create_time timestamp(6));
insert into tbl(ival,description,create_time) select (random()*(2*10^9))::integer as ival,substr('abcdffddddddd',1,(random()*26)::integer)
as description,date(generate_series(now(),now()+'1 week','1 day')) as create_time from generate_series(1,2000000);
postgres=# select pg_size_pretty(pg_relation_size('tbl'::regclass));
pg_size_pretty
----------------
850 MB
(1 row)
通过上述命令看到 tbl 表的大小目前为 850MB ,执行一些 UPDATE 操作后再次查看数据文件
postgres=# ! ls -lh /usr/local/pgsql/data/base/13892/24654*
-rw-------. 1 postgres postgres 1.0G Oct 24 22:01 /usr/local/pgsql/data/base/13892/24654
-rw-------. 1 postgres postgres 677M Oct 24 22:02 /usr/local/pgsql/data/base/13892/24654.1
-rw-------. 1 postgres postgres 448K Oct 24 22:02 /usr/local/pgsql/data/base/13892/24654_fsm
-rw-------. 1 postgres postgres 32K Oct 24 22:01 /usr/local/pgsql/data/base/13892/24654_vm
如前文所述,数据文件的命名规则为 <relfilenode> .<顺序号>, tbl 表的大小超过 lGB,tbl 表的 relfilenode 为 24591 ,超出 lGB 之外的 数据会按每 GB 切割 , 在文件系统中查看时就是名称为 24654.1 的数据文件 。 在上述输出结果中,后缀为_fsm 和_vm 的这两个表文件的附属文件是空 闲空 间映射表文件和可见性映射表文件 。 空 闲空间映射用来映射表文件中可用的空 间 ,可见性映射表文件跟踪哪些页面只包含己知对所有活动事务可见的元组,它
也跟踪哪些页面只包含未被冻结的元组 。 图 5 -2 显示了 PostgreSQL 数据目录、表空间以及文件的结构概貌 。
将保存在磁盘中的块称为 Page ,而将内存中的块称为 Buffer ,表和
索引称为 Relation ,行称为 Tuple。 数据的读写是以 Page 为最小单位, 每个Page 默认大小为 8kB ,在编译 时指定的 BLOCKSZ 大小决定 Page 的大小 。 每个表文件由多个 BLOCKSZ 字节大小的 Page 组成, 每个 Page 包含若干 Tuple 。 对于 I/O 性能较好的硬件,并且以分析为主的数据库,适当增加 BLCKSZ 大小可以小幅提升数据库性能 。
PageHeader 描述了一个数据页的页头信息,包含页的一些元信息 。 它的结构及其结构指针 PageHeader 的定义如下:
如果一个表由 一个只包含一个堆元组 的页面组成 。 该页面的 pd_lower 指向 第一行指针,并且行指针和 pd_upper 都指向第一个堆元组 。 当第二个元组被插入时,它被放置在第一个元组之后。第二行指针被压入第一行,并指向第二个元组 。 pd_lower 更改为指向第二行指针, pd_upper 更改为第二个堆元组 。 此页面中的其他头数据(例如, pd_lsn, pg_checksum 、 pg_flag )也被重写为适当的值 。
当从数据库中检索数据时有两种典型的访问方法,顺序扫描和 B 树索引扫描 。
顺序扫描通过扫描每个页面中的所有行指针顺序读取所有页面中的所有元组 。 B 树索引扫描时 ,索引文件包含索引 元组 , 每个元组由索引键和指 向目 标堆元组的 TID 组成 。 如果找到了正在查找的键的索引元组, 使用获取的 TID 值读取所需的堆元组 。
每个 Tuple 包含两部分 的内容 ,一部分为 HeapTupleHeader,用来保存 Tuple 的元信息,如图 5-4 所示,包含该 Tuple 的 OID 、 xmin 、 cmin 等 ;另 一部分为 HeapTuple ,用来保存 Tuple 的数据 。
一用户一进程的客户端/服务器的应用程序 。 数据库启动时会启动若干
个进程,其中有 postmaster (守护进程)、 postgres (服务进程)、 syslogger 、 checkpointer 、bgwriter 、 walwriter 等辅助进程 。
postmaster 进程的主要职责有:
除了守护进程 postmaster 和服务进程 postgres 外, PostgreSQL 在运行期间还需要一些
辅助进程才能工作,这些进程包括:
内存分为两大类:本地内存和共享内存,另外还有一些为辅助进程分配
的内存等
本地内存由每个后端服务进程分配以供自己使用,当后端服务进程被 fork 时,每个后端进程为查询分配一个本地内存区域 。 本地内存由 三部分组成: work_mem 、 maintenance_work_mem 和 temp_buffers 。
共享内存在 PostgreSQL 服务器启动时分配,由所有后端进程共同使用 。 共享内存主要由三部分组成 :
PostgreSQL 数据库的文件存储 ,介绍了构成数据库的参数文件、数据文件布局,以及表空间、数据库、数据库对象的逻辑存储结构,简单介绍了
PostgreSQL 的守护进程 、服务进程和辅助进程,介绍了客户端与数据库服务器连接交互方式,从数据库目录到表文件到最小的数据块,从大到小逐层分析了重要的数据文件 。