系统表是PostgreSQL数据库存储架构中的核心组成部分,承担着管理和组织数据库数据空间的重任。它们使得用户定义的数据集合能够以一个或多个表的形式被更有效地组织起来。从本质上讲,系统表也是表对象,但与普通表存储实际数据不同,它们存储的是元数据。
元数据可以理解为描述数据的数据。例如,当用户创建一个包含列(c1 int, c2 text)的表时,列的数据类型int和text会被单独存储在pg_type系统表中,而列名c1和c2则会被存储在pg_attribute系统表中,并与pg_type表形成关联。这些如pg_type和pg_attribute之类的系统表能够建立对用户自定义表的详细描述,因此它们被称为元数据
基础系统表
pg_class
pg_class系统表负责管理数据库中表的对象属性,即当前数据库中所有表的固有属性信息都会统一存储在pg_class系统表中,以下是一些pg_class的关键字段:
Oid oid; // 当前表对象在 pg_class 的唯一标识,pg_class会以oid 为主键建索引
NameData relname; // 对象名字
Oid relnamespace; // 所处的 pg_namespace oid,用来和 pg_namespace系统表建立关联
Oid reltype; // 对象类型,用于和pg_type系统表建立关联
Oid relfilenode; // 当前对象的物理文件名
char relpersistence; // 该对象的存储类型, 'p' 表示永久,即基本持久化类型; //'u' 表示 unlogged,不写wal. // 't' 表示临时表,session 级别的生命周期 char relkind; // 该对象的类型,'r'=普通表,'i'=索引,'v'=视图, 't'=toast 大value, 'c'=符合类型 等 int16 relnatts; // 该对象的属性列的个数
pg_class 本身也是一个对象,所以在 pg_class 中也会存储自己的对象属性信息:
执行
create type map as (string varchar, int_1 int); create table map_test (id int, value map);
后可以看到在pg_class中存储了类型map 和表map_test 的元数据
pg_type
系统表pg_type)负责记录并管理所有的类型定义。例如,在执行
create type map as (string varchar, int_1 int)
;建表语句时,所用到的类型int以及复合类型map都会被存储在pg_type系统表中。关键的字段为:Oid oid; // 类型oid NameData typname; // 类型名字
int16 typlen; // 该类型的长度,对于变长类型则一直是-1,如果是-2则是以null 终止的c字符串。
char typtype; // 该类型的基础类型。 'b'=基本类型,'c'=复合类型, 'd'=域类型, 'e'=枚举类型等
map类型的字段为:
由于map是我们自定义的类型,它在PostgreSQL(PG)内部的对象标识符(Oid)会从16384之后开始生成,以此标识其属于用户自定义对象。而对于普通的int类型,它在数据库初始化时就已经在pg_type中预先创建,并且其Oid也是提前分配好的,以确保全局唯一性。
pg_attribute
系统表pg_atribute详细描述了表中每一个列的属性定义。在pg_class中,我们只能看到这个表对象的列的总数,但每一列的具体类型、名称、长度以及它是第几列等详细的描述信息,都会存储在pg_atribute系统表中。pg_atribute的一些关键字段:
Oid attrelid; // 该列属于哪一个关系对象,关系对象的
oid NameData attname; // 该列的名称
Oid atttypid; // 该列的类型, 指向 pg_type的一条类型
int16 attlen; // 该列的长度,同 pg_type中的 typlen,加速读取attr信息。
int16 attnum; // 该列的index,是 attrelid 的第几列。
map_test表的列信息:
看到 pg_attribute 还为 map_test 默认分配了一些默认不可见的属性列。
系统表关系
PostgreSQL通过pg_class系统表来描述对象的属性,同时结合pg_type和pg_attribute两种系统表来描述列的属性,共同构造了一个基本表的完整列信息。
上图描述了创建过程中涉及的主要系统表信息。当我们执行第一条语句
create type map as (string varchar, int_1 int)
时,根据上图给出的系统表结构,会发生以下事情:-
在pg_attribute系统表中为map类型增加两个列属性:
string
和int_1
,并标识它们各自对应的pg_type中的relid
。创建好的string
和int_1
行会保存各自的attrelid
,这个attrelid
用于指向pg_class
中map
对象对应的oid
。 -
在
pg_type
系统表中创建map
类型。因为map
是由两个基本类型int4
和varchar
组合而成的,所以它的类型是组合类型(composite type)。它的typerelid
也会指向pg_class
中map
对象的oid
。 -
在
pg_class
系统表中创建一行信息,记录它指向的pg_type
中的行oid
以及所属的命名空间(namespace)信息。因为map
对象是类型(type)而不是关系(relation),所以其内部不需要存储数据,也就不需要relam
的支持。
当我们执行第二条语句
create table map_test (id int, value map);
时,系统会在 pg_type
和 pg_attribute
系统表中继续添加相应的行。由于复合类型 map
已经存在,map_test
表中的行只需增加对应的 pg_attribute
信息,无需额外创建 pg_type
行。新增的 id
和 value
行只需让 atttypid
指定 pg_type
中的类型,其中 id
是基本类型,而 value
则是已经创建好的复合类型 map
。同时,pg_class
中会增加属于 map_test
的行。因为 map_test
拥有复合类型的列,并且是一个普通表,所以它还会拥有 toast
和 am
。初始化
在新的PG集群上,即使用户尚未执行任何建表操作,系统表就已经存在。这是因为元数据本身需要在为用户提供服务之前就已经存在。PG在启动数据库时,会生成一系列必要的系统表,并且在定义这些表时会加入
BKI_BOOTSTRAP
标记。系统表的初始化基本流程如下:
主要分为编译 和 Initdb两个阶段。 - 编译主要为了生成
postgres.bki
文件 (关注系统表部分)。 - Initdb 主要为了解析 bki文件并生成 template1
, template0
以及 postgres
数据库。编译
编译阶段 主要是通过
genbki.pl
以及 catalog.pm
生成一个postgres.bki
文件(backend interface)放到 PGHOME/share/postgres.bki
,用于后续 initdb时 根据 这个bki 文件生成表对象。genbki.pl
的输入就是 系统表的 .h
以及 .dat
文件。对于 bootstrap 过程中生成的系统表,源代码的定义中能够看到
BKI_BOOTSTRAP
字段,例如:CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
postgres.bki
文件中的SQL 语句不是标准的sql语法,只是postgres 为了加速initdb的性能设计的伪 sql,能够被 postgres --boot
bootstrap 模式启动的 postgres进程解析。initdb
Initdb 的过程主要是执行 initdb二进制文件,生成
postgres.bki
文件,初始化 template1
, template0
以及 postgres
这三个数据库:1.
bootstrap_template1
中会以 --boot
模式启动数据库,只有这个模式下能够利用 boot_yyparse
解析 postgres.bki
中的特殊sql语法;这里和PG 标准的SQL解析器 yyparser
不同。而且 boot_yyparse
不会走执行器的逻辑。2.
postgres.bki
文件中初始化的系统表信息存储方式和普通的系统表一样,会存储在heap表中。-
初始化
template0
以及postgres
数据库时会 用已经 在 bootstrap 模式下完成初始化的template1
进行初始化。不过并不会继续用bootstrap
模式,而是切换为--single
即 single user模式,因为需要执行标准SQL语法。