目 录
学习参考书籍、网站或博文:
- 参考书籍:《PostgreSQL数据库内核分析》
- PostgreSQL系统目录 官方文档,点击前往
PostgreSQL数据库之系统表学习总结
系统表概述
系统表是PostgreSQL数据库存放结构元数据的地方,如表和列的信息,以及内部统计信息等。正常情况下不应该由用户手动去修改系统表,而是由SQL命令关联的系统表操作自动维护系统表控制信息;例如,CREATE DATABASE向 pg_database表插入一行 — 并且实际上在磁盘上创建该数据库。
[postgres@local99:~/pgdata2/bin]$ ./psql -p 5442
psql (13.0)
Type "help" for help.
postgres=# \d
List of relations
Schema | Name | Type | Owner
--------+----------------+-------+----------
public | pg_buffercache | view | postgres
public | ts | table | postgres
(2 rows)
postgres=# create database db1;
CREATE DATABASE
postgres=# select * from pg_database ;
oid | datname | datdba | encoding | datcollate | datctype | datistemplate | datallowconn | datconnlimit | datlastsysoid | datfrozenxid | datminmxid | dattablespace | datacl
-------+-----------+--------+----------+-------------+-------------+---------------+--------------+--------------+---------------+--------------+------------+---------------+-------------------------------------
13580 | postgres | 10 | 6 | en_US.UTF-8 | en_US.UTF-8 | f | t | -1 | 13579 | 479 | 1 | 1663 |
35285 | db1 | 10 | 6 | en_US.UTF-8 | en_US.UTF-8 | f | t | -1 | 13579 | 479 | 1 | 1663 |
1 | template1 | 10 | 6 | en_US.UTF-8 | en_US.UTF-8 | t | t | -1 | 13579 | 479 | 1 | 1663 | {=c/postgres,postgres=CTc/postgres}
13579 | template0 | 10 | 6 | en_US.UTF-8 | en_US.UTF-8 | t | f | -1 | 13579 | 479 | 1 | 1663 | {=c/postgres,postgres=CTc/postgres}
(4 rows)
postgres=#
系统表的声明及规则
系统表功能的实现代码包含系统表的定义文件和系统表绑定函数实现文件。分别位于源码如下位置:
- 在src/include/catalog目录下有若干以"pg_*.h"的文件,这些文件定义了名为pg_*的系统表对应的数据结构,其中indexing.h文件定义了所有系统表的索引,toasting.h文件定义了所有系统表的TOAST表(TOAST表用于存放普通表中的超长属性值)
- 在src/backend/catalog目录下的pg_*.c文件定义了对pg_*进行相关操作的函数,其中indexing.c文件定义了四个操作系统表索引的函数,toasting.c文件定义了四个操作系统表的TOAST表的函数。
每一个pg_*.h
头文件对应一个系统表的结构定义,包含该系统表具有的列集合,以及一些其他的基本属性。Postgres13开始每一个pg_*.h
文件编译之后会对应由genbki.pl
生成的头文件pg_*_d.h
。(这个头文件含有自动生成的宏定义,并且可能包含其他的宏、枚举声明,因此对于读取特定系统目录的客户端C代码很有用)。
pg_*.h文件声明内容及相关规则:
- 定义CATALOG宏,用于以统一的模式去定义系统表的结构以及用以描述系统表的数据结构;
- CATALOG宏结构中的每一个域会导致出现一个目录列。域也可以用genbki.h中描述的BKI属性宏进行标注,例如可以为域定义默认值(
BKI_DEFAULT(value)
)或者把域标记为可以为空(BKI_FORCE_NULL
)或者不能为空(BKI_FORCE_NOT_NULL
)。CATALOG行也可以用genbki.h中描述的一些其他BKI属性宏标注,用于定义该系统目录整体的其他属性,例如是否为共享的关系(用BKI_SHARED_RELATION
属性宏标注)等。- 所有可变域和可以为空的域在定义时必须被放置在CATALOG结构的最后,并且不能够以结构的域的方式访问。例如,如果尝试设置pg_type.typrelid为NULL,当某段代码引用typetup->typrelid(或者引用typetup->typelem,因为它跟随在typrelid之后)时将会出现失败。会导致随机错误乃至段错误。作为对这类错误的一种部分保护,变长或可以为空的域不应该对C编译器可见。 通过将它们包裹在
#ifdef CATALOG_VARLEN ... #endif
(其中CATALOG_VARLEN
是一个永不被定义的符号)中可以实现这一点- 要求所有应该为非空的列在pg_attribute中也被标记为非空。 如果目录列是定长的并且前面没有任何可以为空的列,bootstrap代码将自动把它标记为NOT NULL。 在这一规则不适用的地方,可以根据需要使用
BKI_FORCE_NOT_NULL
和BKI_FORCE_NULL
标注强制正确的标记。- 如果希望前端代码能看到系统目录头文件中的宏或者其他代码,可以在相应部分的周围写上
#ifdef EXPOSE_TO_CLIENT_CODE ... #endif
,这样genbki.pl会把相应的部分拷贝到pg_xxx_d.h头文件中。前端代码可以包括相应的pg_xxx_d.h头文件
CATALOG宏
定义在genbki.h
文件中,所有系统表定义对应的.h文件中都必须包含genbki.h
文件
/*-------------------------------------------------------------------------
*
* genbki.h
* Required include file for all POSTGRES catalog header files
*
* genbki.h defines CATALOG(), BKI_BOOTSTRAP and related macros
* so that the catalog header files can be read by the C compiler.
* (These same words are recognized by genbki.pl to build the BKI
* bootstrap file from these header files.)
*
*
* Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* src/include/catalog/genbki.h
*
*-------------------------------------------------------------------------
*/
#ifndef GENBKI_H
#define GENBKI_H
/* Introduces a catalog's structure definition */
#define CATALOG(name,oid,oidmacro) typedef struct CppConcat(FormData_,name)
/* 在CATALOG之后用于标注系统目录的BKI属性宏选项(在同一行上) */
#define BKI_BOOTSTRAP /* 在生成 postgres.bki 文件时被转换成bootstrap关键字*/
#define BKI_SHARED_RELATION
#define BKI_ROWTYPE_OID(oid,oidmacro)
#define BKI_SCHEMA_MACRO
/* 属性后可能出现的选项(在同一行上) */
#define BKI_FORCE_NULL
#define BKI_FORCE_NOT_NULL
/* 指定属性字段的默认值 */
#define BKI_DEFAULT(value)
/* 指定自动生成的数组类型的默认值 */
#define BKI_ARRAY_DEFAULT(value)
/*
* Indicates how to perform name lookups, typically for an OID or
* OID-array field
*/
#define BKI_LOOKUP(catalog)
/* The following are never defined; they are here only for documentation. */
/*
* Variable-length catalog fields (except possibly the first not nullable one)
* should not be visible in C structures, so they are made invisible by #ifdefs
* of an undefined symbol. See also MARKNOTNULL in bootstrap.c for how this is
* handled.
*/
#undef CATALOG_VARLEN
/*
* There is code in some catalog headers that needs to be visible to clients,
* but we don't want clients to include the full header because of safety
* issues with other code in the header. To handle that, surround code that
* should be visible to clients with "#ifdef EXPOSE_TO_CLIENT_CODE". That
* instructs genbki.pl to copy the section when generating the corresponding
* "_d" header, which can be included by both client and backend code.
*/
#undef EXPOSE_TO_CLIENT_CODE
#endif /* GENBKI_H */
例如pg_class.h文件的声明及定义如下:
#ifndef PG_CLASS_H
#define PG_CLASS_H
#include "catalog/genbki.h"
#include "catalog/pg_class_d.h"
/* ----------------
* pg_class definition. cpp turns this into
* typedef struct FormData_pg_class
*
* Note that the BKI_DEFAULT values below are only used for rows describing
* BKI_BOOTSTRAP catalogs, since only those rows appear in pg_class.dat.
* ----------------
*/
CATALOG(pg_class,1259,RelationRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83,RelationRelation_Rowtype_Id) BKI_SCHEMA_MACRO
{
/* oid */
Oid oid;
/* class name */
NameData relname;
/* OID of namespace containing this class */
Oid relnamespace BKI_DEFAULT(PGNSP);
/* OID of entry in pg_type for table's implicit row type */
Oid reltype BKI_LOOKUP(pg_type);
/* OID of entry in pg_type for underlying composite type */
Oid reloftype BKI_DEFAULT(0) BKI_LOOKUP(pg_type);
/* class owner */
Oid relowner BKI_DEFAULT(PGUID);
/* access method; 0 if not a table / index */
Oid relam BKI_DEFAULT(heap) BKI_LOOKUP(pg_am);
/* identifier of physical storage file */
/* relfilenode == 0 means it is a "mapped" relation, see relmapper.c */
Oid relfilenode BKI_DEFAULT(0);
......
/* all multixacts in this rel are >= this; it is really a MultiXactId */
TransactionId relminmxid BKI_DEFAULT(1); /* FirstMultiXactId */
#ifdef CATALOG_VARLEN /* variable-length fields start here */
/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
/* access permissions */
aclitem relacl[1] BKI_DEFAULT(_null_);
/* access-method-specific options */
text reloptions[1] BKI_DEFAULT(_null_);
/* partition bound node tree */
pg_node_tree relpartbound BKI_DEFAULT(_null_);
#endif
} FormData_pg_class;
源码编译之后在src/include/catalog可以看到所有系统目录对应生成的pg_*_d.h
文件
pg_*.dat数据文件的格式
部分pg_*.h
文件对应有一个pg_*.dat
数据文件(用于保存初始数据,描述了所有必须被插入到pg_*系统目录的初始行);经由genbki.pl
脚本读取.dat文件,定义bki文件中的insert操作。
每个.dat文件含有Perl数据结构文本,它可以简单地通过eval产生由一个哈希引用数组构成的内存数据结构,每个目录行一个
下面以pg_database.dat来说明.dat文件的编写规则
#----------------------------------------------------------------------
#
# pg_database.dat
# Initial contents of the pg_database system catalog.
#
# Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
# Portions Copyright (c) 1994, Regents of the University of California
#
# src/include/catalog/pg_database.dat
#
#----------------------------------------------------------------------
[
{ oid => '1', oid_symbol => 'TemplateDbOid',
descr => 'default template for new databases',
datname => 'template1', encoding => 'ENCODING', datcollate => 'LC_COLLATE',
datctype => 'LC_CTYPE', datistemplate => 't', datallowconn => 't',
datconnlimit => '-1', datlastsysoid => '0', datfrozenxid => '0',
datminmxid => '1', dattablespace => 'pg_default', datacl => '_null_' },
]
需要注意的点:
- 总体的文件布局是:开方括号,一个或者多个花括号集合(每一个表示一个系统目录行),闭方括号。在每一个闭花括号之后写一个逗号。
- 在每个系统目录行内,写成逗号分隔的key => value对。允许的key是该系统目录的列名,外加上元数据键oid、oid_symbol、array_type_oid以及descr。元数据键是可选的,但是系统目录中定义的列必须全部提供,除非该系统目录的.h文件为该列指定了默认值。
- 所有的值都必须被放在单引号中。用反斜线可以转义值中用到的单引号。
- 空值被表示为_null_(注意没有办法创建就是该字符串的值)。
- 注释以#开头,并且必须位于它们自己的行上。
- 那些是其他系统目录条目的OID的字段值应该用符号名而不是实际的数字OID来表示(在上述例子中,dattablespace包含此类的引用。)
- 在每一对花括号内,元数据域oid、oid_symbol、array_type_oid和descr(如果存在)按照顺序放在最前面,然后以定义时的顺序放上该系统目录自己的域。
- 如果可能,根据需要在域之间插入新行以限制行的长度低于80字符。在元数据域和普通域之间也插入一个新行。
pg_class.dat文件的内容如下:
#----------------------------------------------------------------------
#
# pg_class.dat
# Initial contents of the pg_class system catalog.
#
# Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
# Portions Copyright (c) 1994, Regents of the University of California
#
# src/include/catalog/pg_class.dat
#
#----------------------------------------------------------------------
[
# Note: only bootstrap catalogs, ie those marked BKI_BOOTSTRAP, need to
# have entries here. Be sure that the OIDs listed here match those given in
# their CATALOG and BKI_ROWTYPE_OID macros.
{ oid => '1247',
relname => 'pg_type', reltype => 'pg_type' },
{ oid => '1249',
relname => 'pg_attribute', reltype => 'pg_attribute' },
{ oid => '1255',
relname => 'pg_proc', reltype => 'pg_proc' },
{ oid => '1259',
relname => 'pg_class', reltype => 'pg_class' },
]
postgres.bki文件中可以看到创建pg_class和插入初始数据的内容
在Postgres10初始数据是通过宏DATA(x)和DESCR(x)来定义insert操作。
生成过程
系统表是PostgreSQL数据库系统运行控制信息的来源,由系统统一维护,initdb阶段将用户指定的选项转换成对应的参数,通过外部程序调用的方式执行postgres程序。postgres程序进入bootstrap
模式创建数据集簇,读取后端接口postgres.bki
文件来创建模板数据库template1
,系统表在此阶段生成。后端接口postgres.bki
文件是在编译的过程中由/src/backend/catalog
目录下的脚本程序genbki.sh
读取/src/include/catalog
目录下的以.h
结尾的系统表定义文件(包括系统表索引和TOAST表定义文件)创建,并通常存放在安装目录的share子目录下。
BKI文件是一些用Perl语言写的脚本,这些脚本使PostgreSQL后端能够理解,且以特殊的bootstrap模式来执行之,这种模式允许在不存在系统表的零初始条件下执行数据库函数,而普通的SQL命令要求系统表必须存在。因此BKI文件仅用于初始化数据集簇。
BKI
命令格式如下:
(1)
create [bootstrap] [shared_relation] [without_Oids] tablename tableOid (name1 = type1 [, name2=type2, ...])
创建一个名为tablename并且OID为tableOid的表,表的字段在圆括弧中给出。Bootstrap模式支持下列字段类型:bool、bytea、char、name、int2、int4、regproc、regclass、regtype、text、Oid、tid、xid、cid、int2vector、Oidvector、_int4(数组)、_text(数组)、_Oid(数组)、_char(数组)、_aclitem(数组)
。只有在创建完pg_type并且填充了合适的数据之后才可以创建包含其他类型字段的表。
- 如果指定了
bootstrap
选项,那么将只创建表而不向pg_class等系统表里面插入任何记录。因此这样的表将无法被普通的SQL操作访问,直到系统表中的初始记录用硬方法(BKI的insert命令)插入。这个选项用于创建诸如pg_class等核心的系统表。- 如果指定了
shared_relation
选项,那么该表将作为整个数据集簇的共享表创建(比如创建pg_database)。- 如果指定了
without_Oids
选项,则该表将不会具有OID字段。(注意Postgres13的源码中已不再有宏定义BKI_WITHOUT_OIDS
)(2)
open tablename
打开一个名为tablename的表,准备插入数据。在BKI被执行时,每一个时刻都只有一个表被打开,因此使用open命令时,任何当前已经打开的表都会被关闭。
(3)insert [OID = Oid_value] (value1 value2 ...)
以value1、value2等作为字段值插入一个元组,如果该表具有OID属性,则把Oid_value作为被插入元组的OID。如果Oid_value为零或者省略了OID关键字,将为该插入元组分配系统中可用的下一个OID。
(4)close [tablename]
关闭当前被打开的表。由于每一时刻只有一个表被打开,因此可以省略tablename。
(5)declare [unique] index indexname indexOid on tablename using amname (opclass1 name1 [, ...])
在名为tablename的表上用amname访问方法创建一个OID为indexOid且名为indexname的索引。索引属性是tablename表的name1、name2等属性,在属性上使用的操作符类分别是opclass1、opclass2等。该命令仅仅是创建一个索引的结构,索引的内容(即索引项)并不由该命令填充。
(6)declare toast toasttableOid toastindexOid on tablename
为名为tablename的表创建一个TOAST表。该TOAST表的OID为toasttableOid,其索引的OID为toastindexOid。与declare index命令一样,该命令也不会填充TOAST表的索引。
(7)build indices
对使用5和6两种命令创建的索引进行内容填充。
打开安装目录的share/postgres.bki文件,可以看到pg_class系统表的创建、插入及关闭对应的sql命令(其他系统表类似),这些sql语句被运行在bootstrap模式下的postgres程序调用boot_yyparse
按照 src/backend/bootstrap/bootparse.y
中的语法规则来创建。一个create bootstrap
命令,用于创建其中一个关键表(关键表包括:pg_class、pg_attribute、pg_proc和pg_type
);而不带bootstrap的create命令用于创建非关键表,需要注意的是只有所有的关键表被创建和初始化数据之后,才可以使用open命令打开非关键系统表,因为关键表中存储了所有系统表的模式信息,如果它们没有被建立,open命令无法找到要打开的系统表的模式信息。
新增系统表案例
新增一个系统表并记录初始化的用户和时间
- 1.新建系统表头文件
- 用CATALOG宏说明表的结构。
- 定义结构体指针类型。
- PS:如何选一个未使用过的OID:
在src/include/catalog/unused_oids执行./unused_oids可输出尚未使用的oid。
#ifndef PG_INITDBINFO_H
#define PG_INITDBINFO_H
#include "catalog/genbki.h"
#include "catalog/pg_initdbinfo_d.h"
CATALOG(pg_initdbinfo,2137,InitdbinfoRelationId)
{
NameData username;
NameData initdbtime;
} FormData_pg_initdbinfo;
typedef FormData_pg_initdbinfo *Form_pg_initdbinfo;
#endif
- 2.更改Makefile脚本
在src/backend/catalog/的Makefile中添新增系统表的定义
- src/include/catalog/indexing.h中增加系统表的索引,注意索引oid也需要唯一
- DECLARE_UNIQUE_INDEX定义表的索引。
- 定义代表索引OID的宏。
DECLARE_INDEX(pg_initdbinfo_username_index, 6017, on pg_initdbinfo using btree(username name_ops));
#define InitdbInfoUsernameIndexId 6017
-
增加syscache描述符
在文件src/include/utils/syscache.h中,找到SysCacheIdentifier,为新增的系统表定义syscache 描述符。注意SysCacheIdentifier的所有成员是按字母顺序排列的,要保证新增的这个描述符被放置在合适位置。
-
为新建的索引增加一个cachedesc结构
打开文件/src/backend/utils/cache/syscache.c ,在文件头部添加#include语句,将新建系统表的头文件包含进来。然后找到数组cacheinfo,为刚才新建的索引增加一个cachedesc结构。由于cacheinfo数组元素的顺序,与SysCacheIdentifier是一一对应的,要注意cachedesc结构被放在正确的位置。
格式为:表OID,索引oid,查找关键字个数,查找关键字,和hash桶个数。
{InitdbinfoRelationId, /* INITDBINFOUNAMEID */
InitdbInfoUsernameIndexId,
1,
{
Anum_pg_initdbinfo_username,
0,
0,
0
},
4
},
到这里,系统表就建立好了。重新编译安装程序,查看下是否成功创建系统表。
- 获取用户名和当前时间在初始化的过程中插入该系统表
主要函数如下:
static char *GetCurrentTime(time_t t,char *p)
{
time(&t);
struct tm *q;
q = localtime(&t);
strftime(p,64,"%F %T",q);
return p;
}
static void
setup_initdbinfo(FILE *cmdfd)
{
char time[64] = {0};
time_t t;
GetCurrentTime(t,time);
PG_CMD_PRINTF("INSERT INTO pg_initdbinfo VALUES('%s','%s');\n\n",
get_user_name_or_exit(progname), time);
}
在initialize_data_directory函数中调用setup_initdbinfo即可,编译之后查看系统表中的数据如下:
注意:如果需要在初始化之前就向系统表中插入数据,可以编写一个对应的.dat文件,在该文件中编写需要的初始数据