在数据库组件中,一些组件是专用的,如词法解析只用于SQL引擎;而另外一些组件是公共的,用于整个数据库系统。openGauss的公共组件包括系统表、数据库初始化、多线程架构、线程池、内存管理、多维监控和模拟信号机制等。每个组件实现了一个独立的功能。本篇将从系统表、数据库初始化、多线程架构及线程池技术四个方面对公共组件的源代码实现进行介绍。
一、系统表
系统表又称为数据字典或者元数据,存储管理数据库对象的定义信息,如表、索引、触发器等。用户可通过系统表查询用户定义的具体对象信息,如表的每个字段类型。因为openGauss支持一个实例管理多个数据库,所以系统表分为实例级别的系统表和数据库级别的系统表。实例级别的系统表在一个实例管理的多个数据库之间共享,整个实例只有一份,这些系统表为pg_authid、pg_auth_members、pg_database、pg_db_role_setting 、pg_tablespace、pg_shdepend、pg_shdescription、pg_shseclabel。数据库级别的系统表比如pg_class,pg_depend,pg_index,pg_attribute等,每个数据库各有一份。
(一)系统表的定义
openGauss系统表定义全部在src/include/catalog目录下,每个头文件就是一个系统表的定义。如pg_database.h就是对pg_database的定义。在pg_database.h中,主要包括pg_database的表OID(object identifier,对象标识符)、类型OID、结构体定义、字段个数和每个字段ID(identifier,标识符)枚举值、数据库初始化默认值。下面是代码及其具体解释:
/* pg_database本身也是一张表,DatabaseRelationId表示pg_database在系统表pg_class中的OID为1262(pg_class系统表中保存的是表的定义信息) */
#define DatabaseRelationId 1262
/* pg_database本身也是一个结构类型,DatabaseRelation_Rowtype_Id表示pg_database在系统表pg_type中的OID为1248 */
#define DatabaseRelation_Rowtype_Id 1248
/* pg_database本身也是一个结构类型,DatabaseRelation_Rowtype_Id表示pg_database在系统表pg_type中的OID为1248 */
/* BKI_SHARED_RELATION表示pg_database是实例级别的系统表 */
CATALOG(pg_database,1262) BKI_SHARED_RELATION BKI_ROWTYPE_OID(1248) BKI_SCHEMA_MACRO
{
NameData datname; /* 数据库名称 */
Oid datdba; /* 数据库拥有者 */
int4 encoding; /* 字符集编码 */
NameData datcollate; /* LC_COLLATE 设置值 */
NameData datctype; /* LC_CTYPE 设置值 */
bool datistemplate; /* 是否允许作为模板数据库*/
bool datallowconn; /* 是否允许链接 */
int4 datconnlimit; /* 最大连接数*/
Oid datlastsysoid; /* 系统OID最大值*/
ShortTransactionId datfrozenxid; /* 冻结事务ID,所有小于这个值的事务ID已经冷冻。为了兼容原来的版本,使用32位事务ID*/
Oid dattablespace; /* 数据库的缺省表空间 */
NameData datcompatibility;/* 数据库兼容模式,比如除0是报错还是当做正常处理*/
#ifdef CATALOG_VARLEN /* 下面字段是变长字段 */
aclitem datacl[1]; /* 访问权限*/
#endif
TransactionId datfrozenxid64; /* 冷冻事务ID,64-bit(比特)事务ID */
} FormData_pg_database;
CATALOG的宏定义代码为:
#define CATALOG(name,oid) typedef struct CppConcat(FormData_,name)
因此CATALOG(pg_database,1262)就是对结构体FormData_pg_database的定义。之所以采用CATALOG,是因为这个格式是和BKI(backend interface,后端接口)脚本约定的格式,BKI脚本根据这个格式生成数据库的建表脚本。
接下来是数据库对象字段总数和每个字段ID的值的定义代码,定义这些值的目的主要代码访问数据库对象时清晰、方便维护、避免魔鬼数字(魔鬼数字指在代码中没有具体含义的数字、字符串。魔鬼数字会影响代码可读性,使读者无法理解看到的数字对应的含义,从而难以理解程序的意图)。
#define Natts_pg_database 14
#define Anum_pg_database_datname 1
#define Anum_pg_database_datdba 2
#define Anum_pg_database_encoding 3
#define Anum_pg_database_datcollate 4
#define Anum_pg_database_datctype 5
#define Anum_pg_database_datistemplate 6
#define Anum_pg_database_datallowconn 7
#define Anum_pg_database_datconnlimit 8
#define Anum_pg_database_datlastsysoid 9
#define Anum_pg_database_datfrozenxid 10
#define Anum_pg_database_dattablespace 11
#define Anum_pg_database_compatibility 12
#define Anum_pg_database_datacl 13
#define Anum_pg_database_datfrozenxid64 14
最后是创建数据库时的默认数值。代码中的值表示默认创建template1数据库,DATA的字段值和数据库结构体的值一一对应。对应代码如下:
DATA(insert OID = 1 ( template1 PGUID ENCODING "LC_COLLATE" "LC_CTYPE" t t -1 0 0 1663 "DB_COMPATIBILITY" _null_ 3));
SHDESCR("default template for new databases");
#define TemplateDbOid 1
#define DEFAULT_DATABASE "postgres"
系统表头文件的内容和格式基本类似。
(二)系统表的访问
系统表定义后,接下来介绍数据库在运行过程中对系统表的访问。openGauss对系统表的访问主要是通过syscache机制。syscache机制是一个通用的机制,主要对系统表的数据进行缓存,提升系统表数据的访问速度,详细细节参照具体章节描述。这里主要描述与pg_database相关的部分。pg_database在枚举类型enum SysCacheIdentifier中定义的枚举值有一个:DATABASEOID,表示根据数据库OID访问pg_database系统表。同时需要把pg_database系统表访问模式添加到“struct cachedesc cacheinfo”中。与pg_database相关的代码如下:
{DatabaseRelationId, /* DATABASEOID */
DatabaseOidIndexId,
1,
{ObjectIdAttributeNumber, 0, 0, 0},
4},
这几个值与cachedesc结构体的字段对应,表示pg_database表的OID值、索引的OID值、搜索时有1个key字段、搜索key字段ID为ObjectIdAttributeNumber、初始化为4个hash桶。相关的代码如下:
struct cachedesc {
Oid reloid; /* 缓存的表的OID */
Oid indoid; /* 缓存数据的索引OID */
int nkeys; /* 缓存搜索的key的个数 */
int key[4]; /* key属性的编号*/
int nbuckets; /* 缓存hash桶的个数*/
};
系统表的定义和访问主要逻辑如上问所述。与pg_database相关的SQL命令是ALTER DATABASE、CREATE DATABASE、DROP DATABASE,这些命令执行的结果是把数据库相关的信息存储到pg_database系统表中。
其他系统表的逻辑与pg_database相似,不再重复。
二、数据库初始化
数据库正常启动时需要指定数据目录,数据目录中包括了系统表的初始化数据。数据库初始化的过程会生成这些初始系统表数据文件,该过程由initdb和openGauss进程配合生成。initdb控制执行过程,创建目录和基本的配置文件;openGauss进程负责系统表的初始化。initdb通过PG_CMD_OPEN宏启动openGauss进程,同时打开一个管道流,然后通过解析系统表文件中的SQL命令,并把命令通过PG_CMD_PUTS宏的管道流发给openGauss进程,最后通过PG_CMD_CLOSE宏关闭管道流。PG_CMD_OPEN宏是系统函数popen的封装宏,PG_CMD_PUTS宏是系统函数fputs的封装宏,PG_CMD_CLOSE宏是系统函数pclose的封装宏。交互过程如图1所示。
图1 初始化交互过程图|
initdb在创建template1模板数据库时,命令参数指定了“snprintf_s(cmd, sizeof(cmd), sizeof(cmd) - 1, ""%s" --boot -x1 %s %s", backend_exec, boot_options, talkargs);”,其中“--boot”表示openGauss进程以一个特殊的bootstrap模式运行。在其他初始化系统表时,initdb命令参数指定了“snprintf_s(cmd, sizeof(cmd), sizeof(cmd) - 1, ""%s" %s template1 >%s", backend_exec, backend_options, DEVNULL); ”,其中“static const char* backend_options = "--single "”表示openGauss进程以单用户模式运行。
下面以setup_schema函数为例详细介绍这个过程。相关代码如下:
static void setup_schema(void)
{
PG_CMD_DECL;
char** line;
char** lines;
int nRet = 0;
char* buf_features = NULL;
fputs(_("creating information schema ... "), stdout);
(void)fflush(stdout);
lines = readfile(info_schema_file);
/*
* 使用-j 避免在information_schema.sql反斜杠处理
*/
nRet = snprintf_s(
cmd, sizeof(cmd), sizeof(cmd) - 1, ""%s" %s -j template1 >%s", backend_exec, backend_options, DEVNULL);
securec_check_ss_c(nRet, "