PostgreSQL指南:内幕探索
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

1.2 数据库集簇的物理结构

数据库集簇在本质上就是一个文件目录,即基础目录,包含着一系列子目录与文件。执行initdb 命令会在指定目录下创建基础目录,从而初始化一个新的数据库集簇。通常基础目录的路径会被配置到环境变量PGDATA中,但这并不是必要的。

图1.2展示了一个PostgreSQL数据库集簇的例子。base子目录中的每一个子目录都对应一个数据库,数据库中的每个表和索引都至少在相应子目录下存储为一个文件;还有几个包含特定数据的子目录,以及配置文件。虽然PostgreSQL支持表空间,但是该术语的含义与其他关系型数据库管理系统(Relational Database Management System,RDBMS)不同。PostgreSQL中的表空间对应一个包含基础目录之外数据的目录。

图1.2 数据库集簇示例

后续小节将描述数据库集簇的布局、数据库布局、表和索引相关文件的布局,以及PostgreSQL中表空间的布局。

1.2.1 数据库集簇的布局

官方文档描述了数据库集簇的布局。表1.1中列出了主要的文件与子目录:

表1.1 基本目录下的数据库文件和子目录的布局(参见官方文档)

注:若无特殊说明,本书提及的版本号都是PostgreSQL版本号。

1.2.2 数据库布局

一个数据库与 base 子目录下的一个子目录对应,且该子目录的名称与相应数据库的 oid相同。例如,当数据库sampledb的oid为16384时,它对应的子目录名称就是16384。

    $ 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:

    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。

    $ 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)。

    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 或名称返回关系对应的文件路径,非常实用。

    sampledb=# SELECT pg_relation_filepath('sampletbl');
    pg_relation_filepath
    ----------------------
    base/16384/18812
    (1 row)

当表和索引的文件大小超过1GB时,PostgreSQL会创建并使用一个名为relfilenode.1的新文件。如果新文件也填满了,则会创建下一个名为relfilenode.2的新文件,以此类推。

译者注:数据库系统中的表与关系代数中的关系之间的联系紧密但又不尽相同。在PostgreSQL中,表、索引、TOAST表都归类为关系。

    $ 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。这些实际上是空闲空间映射和可见性映射文件,分别存储了表文件每个页面上的空闲空间信息与可见性信息(更多细节见第 5.3.4节和第 6.2节)。索引没有可见性映射文件,只有空闲空间映射文件。

一个具体的示例如下所示:

    $ 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)可能会有4种分支,分支编号分别为0、1、2、3,0号分支main为关系数据文件本体,1号分支fsm保存了main分支中空闲空间的信息,2号分支vm保存了main分支中可见性的信息,3号分支init是很少见的特殊分支,通常表示不被日志记录(unlogged)的表与索引。

每个分支都会被存储为磁盘上的一或多个文件:PostgreSQL会将过大的分支文件切分为若干个段,以免文件的尺寸超过某些特定文件系统允许的大小,也便于一些归档工具进行并发复制,默认的段大小为1GB。

1.2.4 PostgreSQL中表空间的布局

PostgreSQL中的表空间是基础目录之外的附加数据区域。8.0版本中引入了该功能。

图1.3展示了表空间的内部布局,以及表空间与主数据区域的关系。

图1.3 数据库集簇的表空间及其与主数据区域的关系

执行 CREATE TABLESPACE语句会在指定的目录下创建表空间。在该目录下还会创建版本特定的子目录(例如PG_9.4_201409291)。版本特定的命名方式为:

    PG_主版本号_目录版本号

举个例子,如果在/home/postgres/tblspc中创建一个表空间new_tblspc,其oid为16386,则会在表空间下创建一个名如PG_9.4_201409291的子目录。

    $ ls -l /home/postgres/tblspc/
    total 4
    drwx------ 2 postgres postgres 4096 Apr 21 10:08 PG_9.4_201409291

表空间目录通过pg_tblspc子目录中的符号链接寻址,链接名称与表空间的oid值相同。

    $ ls -l $PGDATA/pg_tblspc/
    total 0
    lrwxrwxrwx 1 postgres postgres 21 Apr 21 10:08 16386 -> /home/postgres/tblspc

如果在该表空间下创建新的数据库(oid为16387),则会在版本特定的子目录下创建相应的目录。

    $ ls -l /home/postgres/tblspc/PG_9.4_201409291/
    total 4
    drwx------ 2 postgres postgres 4096 Apr 21 10:10 16387

如果在该表空间内创建一个新表,但新表所属的数据库却创建在基础目录下,那么 PG 会首先在版本特定的子目录下创建名称与现有数据库oid相同的新目录,然后将新表文件放置在刚创建的目录下。

    sampledb=# CREATE TABLE newtbl (.....) TABLESPACE new_tblspc;

    sampledb=# SELECT pg_relation_filepath('newtbl');
                pg_relation_filepath
    ----------------------------------------------
     pg_tblspc/16386/PG_9.4_201409291/16384/18894