searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

元数据与系统表

2023-09-27 02:09:05
52
0

元数据与系统表

系统表是整个 PostgreSQL 数据库存储体系中最重要的一部分数据,它们用来组织管理PostgreSQL 的数据空间。它们本质也是一个个表对象,相比于普通表来说,系统表存储的是元数据。
元数据(Metadata)可以理解为描述数据的数据,使数据更容易理解,查找,管理和使用,比如,用户创建的表有 create table t1(c1 int, c2 text)两种列类型,这一些类型 int, text 会被单独存放在 pg_type的系统表中,同时 c1, c2 列名字则会被存放在 pg_attribute的系统表中,并和 pg_type 形成关联。表名t1会被存在pg_class系统表中。

OID

Oid 在 PostgreSQL 中被用来描述一个个数据表的逻辑对象,比如 Relation, type, attr, namespace等等,每创建一个对象都会为其分配一个属于自己的标识(Oid)。PG 也会通过 Oid 来在不同的数据表之间建立关联,也就是说有一些对象是全局唯一的(pg_class 表的oid)。但是因为 Oid 是 unsigned int,所以当对象的数量超过42亿之后可能会有回卷,所以PG 对Oid的划分有一些自己的定义,比如预留16383 个Oid 作为全局唯一的对象标识,其他的都是给用户表使用,允许发生回卷。
接下来看看 PostgreSQL 内部非常重要的一些系统表,以及它们之间的关系模型,从而更好的帮助我们理解创建的一个用户表是如何被组织管理的。

主要系统表

pg_class(表及与表类似结构的数据库对象信息)

用于存储表及与表类似结构的数据库对象信息,包含索引、序列、视图、复合数据类型、TOAST表等。每一个对象都在pg_class中表示为一个元组。
代码路径:
src/include/catalog/pg_class.h
// 因为过多,简单挑几个关键信息如下,pg_class 的唯一标识 
CATALOG(pg_class,1259,RelationRelationId)
{
    Oid         oid; // 当前表对象在 pg_class 的唯一标识,pg_class会以oid 为主键建索引,方便查找
    NameData    relname; // relation 名字
    Oid         relnamespace; // 所处的 pg_namespace oid,用来和 pg_namespace系统表建立关联
    Oid         reltype; // 对象类型,用于和pg_type系统表建立关联
    ...
    Oid         relam; // am 类型,比如是heap or 其他的,也是和 pg_amthod 建立管理
    ...
    Oid         relfilenode; // 当前对象的物理文件名,pg 内部文件名都是以数字存在。
    ...
    char        relpersistence; // 该对象的存储类型, 'p' 表示永久,即基本持久化类型; 
                                //'u' 表示 unlogged,不写wal.
                                // 't' 表示临时表,session 级别的生命周期
    char        relkind; // 该对象的类型,'r'=普通表,'i'=索引,'v'=视图, 't'=toast 大value, 'c'=符合类型 等
    int16       relnatts; // 该对象的属性列的个数
    ...
}
可以看到通过 create type map as (string varchar, int_1 int); create table map_test (id int, value map); 创建的表在 pg_class 中存储的属性信息 有两个,一个是 类型 map 的属性信息, 一个是表 map_test的属性信息。
postgres=# create type map as (string varchar, int_1 int); 
CREATE TYPE
postgres=# create table map_test (id int, value map); 
CREATE TABLE
//复合类型 map 的属性信息
postgres=# select oid,relname,relnamespace,reltype,relam,relfilenode,relpersistence,relkind,relnatts from pg_class where relname='map';
  oid  | relname | relnamespace | reltype | relam | relfilenode | relpersistence | relkind | relnatts 
-------+---------+--------------+---------+-------+-------------+----------------+---------+----------
 16396 | map     |         2200 |   16398 |     0 |       16396 | p              | c       |        2
(1 row)

//表map_test的属性信息
postgres=# select oid,relname,relnamespace,reltype,relam,relfilenode,relpersistence,relkind,relnatts from pg_class where relname='map_test';
  oid  | relname  | relnamespace | reltype | relam | relfilenode | relpersistence | relkind | relnatts 
-------+----------+--------------+---------+-------+-------------+----------------+---------+----------
 16399 | map_test |         2200 |   16401 |     0 |       16399 | p              | r       |        2
(1 row)

pg_type(数据类型信息)

用于存储数据类型信息。基本数据类型和枚举类型由CREATE TYPE创建,域类型由CREATE DOMAIN创建,复合数据类型在表创建时自动创建。pg_type中每一个元组对应一个数据类型。比如上面的 create table map_test (id int, value map); 建表过程中用到的类型 int 以及 复合类型 map 都会被存储到 pg_type中,而列名字 id 以及 value 则会被存储的pg_attribute 系统表中,这个后面会说。
接下来看看pg_type 的定义 pg_type.h,挑选几个简略定义如下:
// pg_type的 固有对象 标识是1247
CATALOG(pg_type,1247,TypeRelationId)
{
    Oid         oid; // 类型oid
    NameData    typname; // 类型名字
    ...
    int16       typlen; // 该类型的长度,对于变长类型则一直是-1,如果是-2则是以null 终止的c字符串。
    ...
    char        typtype; // 该类型的基础类型。 'b'=基本类型,'c'=复合类型, 'd'=域类型, 'e'=枚举类型等
}
 
比如对于我们前面通过 create type map as (string varchar, int_1 int); 创建的类型,可以从 pg_type中看到其信息如下
postgres=# select oid,typname,typlen,typtype from pg_type where typname='map';
  oid  | typname | typlen | typtype 
-------+---------+--------+---------
 16398 | map     |     -1 | c
(1 row)
 

pg_attribute(表的属性信息)

用于存储表的属性信息。对于数据库中表的每个属性都有一个元组。在 pg_class中我们看到的是这个表对象的列的个数,但是具体每一个列 都是什么类型,名字是什么,长度是多少,是第几列等这样的列的描述信息则是会存储在 pg_attribute 系统表中。 其基本类型定义如下pg_attribute.h
CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BKI_SCHEMA_MACRO
{
    Oid         attrelid; // 该列属于哪一个关系对象,关系对象的oid (一个数据库只能有一个关系对象的名字)
    NameData    attname; // 该列的名称
    Oid         atttypid; // 该列的类型, 指向 pg_type的一条类型
    ...
    int16       attlen; // 该列的长度,同 pg_type中的 typlen,加速读取attr信息。
    int16       attnum; // 该列的index,是 attrelid 的第几列。
    ...
}
 
比如我们查看 前面创建的 test_mapcreate table map_test (id int, value map);) 表的列描述信息如下:
可以看到 pg_attribute 还为 map_test 默认分配了一些默认不可见的属性列,用作 extension 时查看更细粒度的tuple信息。 用户自己创建的两列 idvalue 则是有自己的typeid信息,可以从 pg_type 中看到其定义。
 

pg_namespace(命名空间)

用于存储命名空间,PostgreSQL的名字空间层次是:数据库.模式.表.属性。pg_namespace中每一个元组都对应一个名字空间。
代码路径:(src/backend/catalog/pg_namespace.h)
 
CATALOG(pg_namespace,2615)
{
    NameData    nspname;//命名空间
    Oid            nspowner;//命名空间所有者的oid

#ifdef CATALOG_VARLEN            /* variable-length fields start here */
    aclitem        nspacl[1];
#endif
} FormData_pg_namespace;
 
查询命名空间,返回"pg_namespace"表的所有行和列,其中包含命名空间的详细信息:
 
//如命名空间的名称、所属数据库的OID(对象标识符)以及其他相关属性。
postgres=# select * from pg_namespace;
      nspname       | nspowner |           nspacl           
--------------------+----------+----------------------------
 pg_toast           |       10 | 
 pg_oracle          |       10 | 
 pg_temp_1          |       10 | 
 pg_toast_temp_1    |       10 | 
 pg_catalog         |       10 | {tbase=UC/tbase,=U/tbase}
 public             |       10 | {tbase=UC/tbase,=UC/tbase}
 information_schema |       10 | {tbase=UC/tbase,=U/tbase}
(7 rows)

pg_tablespace(表空间)

  • 用于存储表空间信息,将表放置在不同的表空间有助于实施磁盘文件布局。数据库在逻辑上分成多个存储单元,称作表空间。表空间用作把逻辑上相关的结构放在一起。数据库逻辑上是由一个或多个表空间组成。初始化的时候,会自动创建pg_default和pg_global两个表空间。
  • pg_tablespace在整个数据集簇里只有一份,也就是说同一个数据集簇内的所有数据库共享一个pg_tablespace表,而不是每个数据库都有自己的pg_tablespace表。pg_tablespace中每一个元组都对应一个表空间。
定义在pg_tablespace.h中:
CATALOG(pg_tablespace,1213) BKI_SHARED_RELATION
{
    NameData    spcname;        /*表空间名字 */
    Oid            spcowner;        /* 表空间的所有者的oid */

#ifdef CATALOG_VARLEN            /* variable-length fields start here */
    aclitem        spcacl[1];        /* access permissions */
    text        spcoptions[1];    /* per-tablespace options */
#endif
} FormData_pg_tablespace;
查询表空间:
 

pg_database(数据库信息)

用于存储当前数据集簇中数据库的信息,是一个在整个集簇范围内共享的系统表。pg_database中每一个元组都对应集簇中的一个数据库。
代码路径:src/include/catalog/pg_database.h
postgres=# select * from pg_database;
  datname  | datdba | encoding | datcollate |  datctype  | datistemplate | datallowconn | datconnlimit | datlastsysoid | datfrozenxi
d | datminmxid | dattablespace |           datacl           
-----------+--------+----------+------------+------------+---------------+--------------+--------------+---------------+------------
--+------------+---------------+----------------------------
 postgres  |     10 |        6 | en_US.utf8 | en_US.utf8 | f             | t            |           -1 |         13368 |            
3 |          1 |          1663 | 
 template1 |     10 |        6 | en_US.utf8 | en_US.utf8 | t             | t            |           -1 |         13368 |            
3 |          1 |          1663 | {=c/tbase,tbase=CTc/tbase}
 template0 |     10 |        6 | en_US.utf8 | en_US.utf8 | t             | f            |           -1 |         13368 |            
3 |          1 |          1663 | {=c/tbase,tbase=CTc/tbase}
(3 rows)

//数据库信息
postgres=# \l
                             List of databases
   Name    | Owner | Encoding |  Collate   |   Ctype    | Access privileges 
-----------+-------+----------+------------+------------+-------------------
 postgres  | tbase | UTF8     | en_US.utf8 | en_US.utf8 | 
 template0 | tbase | UTF8     | en_US.utf8 | en_US.utf8 | =c/tbase         +
           |       |          |            |            | tbase=CTc/tbase
 template1 | tbase | UTF8     | en_US.utf8 | en_US.utf8 | =c/tbase         +
           |       |          |            |            | tbase=CTc/tbase
(3 rows)

pg_index(索引)

用于存储索引的具体信息。
postgres=# select * from pg_index limit 5;
 indexrelid | indrelid | indnatts | indisunique | indisprimary | indisexclusion | indimmediate | indisclustered | indisvalid | indcheck
xmin | indisready | indislive | indisreplident | indkey | indcollation | indclass  | indoption | indexprs | indpred 
------------+----------+----------+-------------+--------------+----------------+--------------+----------------+------------+---------
-----+------------+-----------+----------------+--------+--------------+-----------+-----------+----------+---------
       2831 |     2830 |        2 | t           | t            | f              | t            | f              | t          | f       
     | t          | t         | f              | 1 2    | 0 0          | 1981 1978 | 0 0       |          | 
       2833 |     2832 |        2 | t           | t            | f              | t            | f              | t          | f       
     | t          | t         | f              | 1 2    | 0 0          | 1981 1978 | 0 0       |          | 
       2835 |     2834 |        2 | t           | t            | f              | t            | f              | t          | f       
     | t          | t         | f              | 1 2    | 0 0          | 1981 1978 | 0 0       |          | 
       2837 |     2836 |        2 | t           | t            | f              | t            | f              | t          | f       
     | t          | t         | f              | 1 2    | 0 0          | 1981 1978 | 0 0       |          | 
       2839 |     2838 |        2 | t           | t            | f              | t            | f              | t          | f       
     | t          | t         | f              | 1 2    | 0 0          | 1981 1978 | 0 0       |          | 
(5 rows)

postgres=# select * from pg_class where oid =2831;
       relname       | relnamespace | reltype | reloftype | relowner | relam | relfilenode | reltablespace | relpages | reltuples | rel
allvisible | reltoastrelid | relhasindex | relisshared | relpersistence | relkind | relnatts | relchecks | relhasoids | relhaspkey | re
lhasrules | relhastriggers | relhassubclass | relrowsecurity | relforcerowsecurity | relispopulated | relreplident | relispartition | r
elhasextent | relpartkind | relparent | relfrozenxid | relminmxid | relacl | reloptions | relpartbound 
---------------------+--------------+---------+-----------+----------+-------+-------------+---------------+----------+-----------+----
-----------+---------------+-------------+-------------+----------------+---------+----------+-----------+------------+------------+---
----------+----------------+----------------+----------------+---------------------+----------------+--------------+----------------+--
------------+-------------+-----------+--------------+------------+--------+------------+--------------
 pg_toast_2604_index |           99 |       0 |         0 |       10 |   403 |        2831 |             0 |        1 |         0 |    
         0 |             0 | f           | f           | p              | i       |        2 |         0 | f          | f          | f 
          | f              | f              | f              | f                   | t              | n            | f              | f
            |             |         0 |            0 |          0 |        |            | 
(1 row)
主要系统表:
  1. 系统表 pg_namespace 用于存储命名空间
  2. pg_tablespace 存储表空间信息,将表放置在不同的表空间有助于实施磁盘文件布局。
  3. pg_database中存放 了当前数据集簇中数据库的信息,它也是一个在整个集簇范围内共亭的系统表.
  4. pg_c1ass 存储表及与表类似结构的数据库对象信息,包含索引、序列、视图、复合数据类型、 TOAST 表等。
  5. pg_type存储数据类型信息
  6. pg_attribute 存储表的属性信息,对于数据库中表的每个属性都有一个元组。
  7. pg_index 存储索引的具体信息

关键系统表之间的相互依赖关系

pg_namespace表中的命名空间OID 与pg_class表中的命名空间OID相关联,用于确定表所属的命名空间。
pg_class表中的表OID与pg_attribute表中的表OID相关联,用于确定每个表的列信息。
pg_class表中的表OID与pg_index表中的表OID相关联,用于确定每个表的索引信息。

系统表初始化

在PostgreSQL数据库安装完后,需要先进行初始化数据库操作(initdb),生成模板数据库和相应的目录、文件信息,系统表即在此阶段生成。而用户数据库及其系统表都是从模板数据库进行复制生成的。

系统表的访问

由于系统表保存了数据库的所有元数据,所以系统运行时对系统表的访问是非常频繁的。为了 提高系统性能,在内存中建立了共享的系统表CACHE,使用 Hash 函数和 Hash 表提高查询效率。
PG 的 cache 体系 主要有三种:
  • syscache,缓存系统表数据
  • relcache,缓存 一个表关系relation 的完整数据(包括用户表的)
  • plancache,缓存planstmt,加速一个query对同一个planstmt 的访问
代码路径:src/backend/utils/cache/
主要介绍的是前两种:

SysCache(CatCache

SysCache介绍

代码路径:src/include/utils/catcache.h
  • 缓存系统表数据,也叫 CatCache catalog cache,catcache中存放最近访问过的系统表缓存。
  • 从实现上看SysCache就是一个数组,数组的长度为预定义的系统表的个数。在PG 8.4.1中实现了54个系统表,因此SysCache数组具有54个元素。每个元素的数据结构为CatCache,该结构体内使用Hash来存储被缓存的系统表元组,每个系统表唯一地对应一个SysCache数组中的CatCache结构。
  • 在Postgres进程初始化时(在InitProgres中),将会对SysCache进行初始化。SysCache的初始化实际上是填充SysCache数组中每个元素的CatCache结构的过程,主要任务是将査找系统表元组的关键字信息写入SysCache数组元素中。这样通过指定的关键字可以快速定位到系统表元组的存储位置。
  • CatCache的数据结构如下:
  • 在SysCache.c文件中已经将所有系统表的CatCache信息存储在一个名为cacheinfo的静态数组 中,每个系统表的CatCache信息用一个数组元素来描述,其数据类型为cachedesc:
 
struct cachedesc
{
        Oid                        reloid;                        /* OID of the relation being cached */
        Oid                        indoid;                        /* OID of index relation for this cache */
        int                        nkeys;                        /* # of keys needed for cache lookup */
        int                        key[4];                        /* attribute numbers of key attrs */
        int                        nbuckets;                /* number of hash buckets for this cache */
};
为了便于查找, SysCache 中的 CatCache 通过其 cc_next 字段构成了一个单向链表,其头部用全
局变量CacheHdr 记录(数据结构为catcacheheader):
 
typedef struct catcacheheader
{
    slist_head    ch_caches;        //指向 CatCache 链表的头部
    int            ch_ntup;        //所有CatCache中缓存元组的总数
} CatCacheHeader;

SysCache初始化

  • 第一阶段:PG 在初始化 backend进程时 会通过 InitPostgres --> InitCatalogCache 完成对 SysCache 的初始化,并建立由CacheHdr记录的CatCache链表, 这里 SysCache 是一个 CatCache 结构的数组。InitCatalogCache函数中对SysCache的初始化主要分为以下几个步骤:
    • 根据cacheinfo为SysCache数组分配空间,这里将SysCache的长度设置为和cacheinfo数组相同。
    • 循环调用InitCatcache函数根据cacheinfo中的每一个元素生成CatCache结构并放入SysCache数组的对应位置中。InitCatcache每调用一次将处理一个cachedesc结构。该函数根据cachedesc中要求的Hash桶的数量 为即将建立的CatCache结构分配内存,并根据cachedesc结构中的信息填充CatCache的各个字段。 最后将生成的CatCache链接在CacheHdr所指向的链表的头部。
  • 第二阶段:调用函数RelationCacheInitializePhase2(负责RelCache的初始化)进行第二阶段初始化,该函数调用InitCatcachePhase2。InitCatcachePhase2将依次完善SysCache数组中的CatCache结构,主要是根据对应的系统表填充CatCache结构中的元组描述符(cc_tupledesc)、系统表名(cc_relname)、查找关键字的相关字段(cc_hashfunc、cc_isname、cc_skey)等。
  • SysCache数组初始化后,CatCache内是没有任何元组的,但是随着系统运行,对于系统表元组的访问,其中的系统表元组也会逐渐增多。

RelCache

RelCache 介绍

  • RelCache 中包含所有最近访问过的表的模式信息(包含系统表的信息) 。RelCache 的管理比 SysCache 要简单许多,原因在于大多数时候 RelCache 中存储的 RelationData 的结构是不变的,因此 PG仅用一个 Hash 表管理RelCache 。对 RelCache 的查找、 插入、删除、修改等操作也非常简单。当需要打开一个表时,首先在 RelCache 中寻找该表的 RelationData 结构,如果没有找到,则创建该结构并加入到 RelCache 中。
  • RelCache 中存放的不是元组,而是 RelationData 数据结构:

RelCache初始化

与SysCache 的初始化类似, RelCache 的初始化同样也在 lnitPostgres 函数中进行,同样分为两个阶段: RelationCachelnitialize 和RelationCacheInitializePhase2。
  • 第一阶段:InitPostgres会调用函数RelationCachelnitialize对ReiCache进行第一阶段初始化,该函数将为该进程创建一个Hash表,其Hash键为表的OID,并设置Hash函数为oid_hash。Hash表的创建在函数hash_create中实现,该函数将创建一个标准Hash表结构体HTAB。
  • 第二阶段:在完成了 Hash表的创建后,InitPostgres将调用RelationCachelnitializePhase进人第二阶段的初始化。该函数将必要的系统表和系统表索引的模式信息加人到RelCache中,这个过程通过函数RelationCacheInitializePhase2 来实现。这个阶段会确保 pg_class、pg_attribute、pg_proc、pg_type 四个系统表及相关索引的模式信息被加入到RelCache。在PostgreSQL中,使用一个文件pgJntemaLinit来记录系统表RelationData结构体,若该文件存在且未损坏,则将其内容直接读人RelCache中。否则,分别建立 pg_class、pg__atlribute、pg_proc、pg_type 及其索引的 RelationData 结构,加入到 RelCache上的Hash表中,并重写pg_internal.init文件。
  • 当RelCache初始化完成后,我们就可以使用它来査找表的模式信息。RelCache的主要操作包括:

RelCache的主要操作

插人新打开的表
当打开新的表时,要把它的RelationData加入到RelCache中。该操作通过宏RelationCachelnsert来实现:首先,根据关系表OID在Hash表中找到对应的位置,调用函数hash_search,指定査询模式为HASH_ENTER,该模式下若发现OID对应的Hash桶已存在,则返回其指针;否则创建一个新的空的hash桶,返回其指针。然后将返回的指针强制转换为RelIdCacheEnt(数据结构:relidcacheent),然后把打开表的RelationData陚值给reldesc字段。
 
typedef struct relidcacheent
{
    Oid                        reloid;
        Relation        reldesc;
} RelIdCacheEnt;
 
査找Hash表
査找Hash表通过定义宏RelationldCacheLookup (ID,RELATION)来实现,调用函数hash_search,指定査询模式为HASH_FIND,若找到ID对应的RelldCacheEnt,则将其iddesc字段的值賦值给 RELATION;否则,设置RELATION为NULL。
从Hash表中删除元素
从Hash表中删除元素通过定义宏RelationCacheDelete(RELATION)来实现,调用函数hash_search,指定査询模式为HASH_REVOKE,在该模式下,若找到对应的Hash桶,则将其删除;否则返回NULL。
 
Hash表实际上扮演了 RelCache索引的角色,所有对于RelCache的査找都是通过Hash表辅助进行的。
 
0条评论
0 / 1000
蒋****典
5文章数
0粉丝数
蒋****典
5 文章 | 0 粉丝
蒋****典
5文章数
0粉丝数
蒋****典
5 文章 | 0 粉丝
原创

元数据与系统表

2023-09-27 02:09:05
52
0

元数据与系统表

系统表是整个 PostgreSQL 数据库存储体系中最重要的一部分数据,它们用来组织管理PostgreSQL 的数据空间。它们本质也是一个个表对象,相比于普通表来说,系统表存储的是元数据。
元数据(Metadata)可以理解为描述数据的数据,使数据更容易理解,查找,管理和使用,比如,用户创建的表有 create table t1(c1 int, c2 text)两种列类型,这一些类型 int, text 会被单独存放在 pg_type的系统表中,同时 c1, c2 列名字则会被存放在 pg_attribute的系统表中,并和 pg_type 形成关联。表名t1会被存在pg_class系统表中。

OID

Oid 在 PostgreSQL 中被用来描述一个个数据表的逻辑对象,比如 Relation, type, attr, namespace等等,每创建一个对象都会为其分配一个属于自己的标识(Oid)。PG 也会通过 Oid 来在不同的数据表之间建立关联,也就是说有一些对象是全局唯一的(pg_class 表的oid)。但是因为 Oid 是 unsigned int,所以当对象的数量超过42亿之后可能会有回卷,所以PG 对Oid的划分有一些自己的定义,比如预留16383 个Oid 作为全局唯一的对象标识,其他的都是给用户表使用,允许发生回卷。
接下来看看 PostgreSQL 内部非常重要的一些系统表,以及它们之间的关系模型,从而更好的帮助我们理解创建的一个用户表是如何被组织管理的。

主要系统表

pg_class(表及与表类似结构的数据库对象信息)

用于存储表及与表类似结构的数据库对象信息,包含索引、序列、视图、复合数据类型、TOAST表等。每一个对象都在pg_class中表示为一个元组。
代码路径:
src/include/catalog/pg_class.h
// 因为过多,简单挑几个关键信息如下,pg_class 的唯一标识 
CATALOG(pg_class,1259,RelationRelationId)
{
    Oid         oid; // 当前表对象在 pg_class 的唯一标识,pg_class会以oid 为主键建索引,方便查找
    NameData    relname; // relation 名字
    Oid         relnamespace; // 所处的 pg_namespace oid,用来和 pg_namespace系统表建立关联
    Oid         reltype; // 对象类型,用于和pg_type系统表建立关联
    ...
    Oid         relam; // am 类型,比如是heap or 其他的,也是和 pg_amthod 建立管理
    ...
    Oid         relfilenode; // 当前对象的物理文件名,pg 内部文件名都是以数字存在。
    ...
    char        relpersistence; // 该对象的存储类型, 'p' 表示永久,即基本持久化类型; 
                                //'u' 表示 unlogged,不写wal.
                                // 't' 表示临时表,session 级别的生命周期
    char        relkind; // 该对象的类型,'r'=普通表,'i'=索引,'v'=视图, 't'=toast 大value, 'c'=符合类型 等
    int16       relnatts; // 该对象的属性列的个数
    ...
}
可以看到通过 create type map as (string varchar, int_1 int); create table map_test (id int, value map); 创建的表在 pg_class 中存储的属性信息 有两个,一个是 类型 map 的属性信息, 一个是表 map_test的属性信息。
postgres=# create type map as (string varchar, int_1 int); 
CREATE TYPE
postgres=# create table map_test (id int, value map); 
CREATE TABLE
//复合类型 map 的属性信息
postgres=# select oid,relname,relnamespace,reltype,relam,relfilenode,relpersistence,relkind,relnatts from pg_class where relname='map';
  oid  | relname | relnamespace | reltype | relam | relfilenode | relpersistence | relkind | relnatts 
-------+---------+--------------+---------+-------+-------------+----------------+---------+----------
 16396 | map     |         2200 |   16398 |     0 |       16396 | p              | c       |        2
(1 row)

//表map_test的属性信息
postgres=# select oid,relname,relnamespace,reltype,relam,relfilenode,relpersistence,relkind,relnatts from pg_class where relname='map_test';
  oid  | relname  | relnamespace | reltype | relam | relfilenode | relpersistence | relkind | relnatts 
-------+----------+--------------+---------+-------+-------------+----------------+---------+----------
 16399 | map_test |         2200 |   16401 |     0 |       16399 | p              | r       |        2
(1 row)

pg_type(数据类型信息)

用于存储数据类型信息。基本数据类型和枚举类型由CREATE TYPE创建,域类型由CREATE DOMAIN创建,复合数据类型在表创建时自动创建。pg_type中每一个元组对应一个数据类型。比如上面的 create table map_test (id int, value map); 建表过程中用到的类型 int 以及 复合类型 map 都会被存储到 pg_type中,而列名字 id 以及 value 则会被存储的pg_attribute 系统表中,这个后面会说。
接下来看看pg_type 的定义 pg_type.h,挑选几个简略定义如下:
// pg_type的 固有对象 标识是1247
CATALOG(pg_type,1247,TypeRelationId)
{
    Oid         oid; // 类型oid
    NameData    typname; // 类型名字
    ...
    int16       typlen; // 该类型的长度,对于变长类型则一直是-1,如果是-2则是以null 终止的c字符串。
    ...
    char        typtype; // 该类型的基础类型。 'b'=基本类型,'c'=复合类型, 'd'=域类型, 'e'=枚举类型等
}
 
比如对于我们前面通过 create type map as (string varchar, int_1 int); 创建的类型,可以从 pg_type中看到其信息如下
postgres=# select oid,typname,typlen,typtype from pg_type where typname='map';
  oid  | typname | typlen | typtype 
-------+---------+--------+---------
 16398 | map     |     -1 | c
(1 row)
 

pg_attribute(表的属性信息)

用于存储表的属性信息。对于数据库中表的每个属性都有一个元组。在 pg_class中我们看到的是这个表对象的列的个数,但是具体每一个列 都是什么类型,名字是什么,长度是多少,是第几列等这样的列的描述信息则是会存储在 pg_attribute 系统表中。 其基本类型定义如下pg_attribute.h
CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BKI_SCHEMA_MACRO
{
    Oid         attrelid; // 该列属于哪一个关系对象,关系对象的oid (一个数据库只能有一个关系对象的名字)
    NameData    attname; // 该列的名称
    Oid         atttypid; // 该列的类型, 指向 pg_type的一条类型
    ...
    int16       attlen; // 该列的长度,同 pg_type中的 typlen,加速读取attr信息。
    int16       attnum; // 该列的index,是 attrelid 的第几列。
    ...
}
 
比如我们查看 前面创建的 test_mapcreate table map_test (id int, value map);) 表的列描述信息如下:
可以看到 pg_attribute 还为 map_test 默认分配了一些默认不可见的属性列,用作 extension 时查看更细粒度的tuple信息。 用户自己创建的两列 idvalue 则是有自己的typeid信息,可以从 pg_type 中看到其定义。
 

pg_namespace(命名空间)

用于存储命名空间,PostgreSQL的名字空间层次是:数据库.模式.表.属性。pg_namespace中每一个元组都对应一个名字空间。
代码路径:(src/backend/catalog/pg_namespace.h)
 
CATALOG(pg_namespace,2615)
{
    NameData    nspname;//命名空间
    Oid            nspowner;//命名空间所有者的oid

#ifdef CATALOG_VARLEN            /* variable-length fields start here */
    aclitem        nspacl[1];
#endif
} FormData_pg_namespace;
 
查询命名空间,返回"pg_namespace"表的所有行和列,其中包含命名空间的详细信息:
 
//如命名空间的名称、所属数据库的OID(对象标识符)以及其他相关属性。
postgres=# select * from pg_namespace;
      nspname       | nspowner |           nspacl           
--------------------+----------+----------------------------
 pg_toast           |       10 | 
 pg_oracle          |       10 | 
 pg_temp_1          |       10 | 
 pg_toast_temp_1    |       10 | 
 pg_catalog         |       10 | {tbase=UC/tbase,=U/tbase}
 public             |       10 | {tbase=UC/tbase,=UC/tbase}
 information_schema |       10 | {tbase=UC/tbase,=U/tbase}
(7 rows)

pg_tablespace(表空间)

  • 用于存储表空间信息,将表放置在不同的表空间有助于实施磁盘文件布局。数据库在逻辑上分成多个存储单元,称作表空间。表空间用作把逻辑上相关的结构放在一起。数据库逻辑上是由一个或多个表空间组成。初始化的时候,会自动创建pg_default和pg_global两个表空间。
  • pg_tablespace在整个数据集簇里只有一份,也就是说同一个数据集簇内的所有数据库共享一个pg_tablespace表,而不是每个数据库都有自己的pg_tablespace表。pg_tablespace中每一个元组都对应一个表空间。
定义在pg_tablespace.h中:
CATALOG(pg_tablespace,1213) BKI_SHARED_RELATION
{
    NameData    spcname;        /*表空间名字 */
    Oid            spcowner;        /* 表空间的所有者的oid */

#ifdef CATALOG_VARLEN            /* variable-length fields start here */
    aclitem        spcacl[1];        /* access permissions */
    text        spcoptions[1];    /* per-tablespace options */
#endif
} FormData_pg_tablespace;
查询表空间:
 

pg_database(数据库信息)

用于存储当前数据集簇中数据库的信息,是一个在整个集簇范围内共享的系统表。pg_database中每一个元组都对应集簇中的一个数据库。
代码路径:src/include/catalog/pg_database.h
postgres=# select * from pg_database;
  datname  | datdba | encoding | datcollate |  datctype  | datistemplate | datallowconn | datconnlimit | datlastsysoid | datfrozenxi
d | datminmxid | dattablespace |           datacl           
-----------+--------+----------+------------+------------+---------------+--------------+--------------+---------------+------------
--+------------+---------------+----------------------------
 postgres  |     10 |        6 | en_US.utf8 | en_US.utf8 | f             | t            |           -1 |         13368 |            
3 |          1 |          1663 | 
 template1 |     10 |        6 | en_US.utf8 | en_US.utf8 | t             | t            |           -1 |         13368 |            
3 |          1 |          1663 | {=c/tbase,tbase=CTc/tbase}
 template0 |     10 |        6 | en_US.utf8 | en_US.utf8 | t             | f            |           -1 |         13368 |            
3 |          1 |          1663 | {=c/tbase,tbase=CTc/tbase}
(3 rows)

//数据库信息
postgres=# \l
                             List of databases
   Name    | Owner | Encoding |  Collate   |   Ctype    | Access privileges 
-----------+-------+----------+------------+------------+-------------------
 postgres  | tbase | UTF8     | en_US.utf8 | en_US.utf8 | 
 template0 | tbase | UTF8     | en_US.utf8 | en_US.utf8 | =c/tbase         +
           |       |          |            |            | tbase=CTc/tbase
 template1 | tbase | UTF8     | en_US.utf8 | en_US.utf8 | =c/tbase         +
           |       |          |            |            | tbase=CTc/tbase
(3 rows)

pg_index(索引)

用于存储索引的具体信息。
postgres=# select * from pg_index limit 5;
 indexrelid | indrelid | indnatts | indisunique | indisprimary | indisexclusion | indimmediate | indisclustered | indisvalid | indcheck
xmin | indisready | indislive | indisreplident | indkey | indcollation | indclass  | indoption | indexprs | indpred 
------------+----------+----------+-------------+--------------+----------------+--------------+----------------+------------+---------
-----+------------+-----------+----------------+--------+--------------+-----------+-----------+----------+---------
       2831 |     2830 |        2 | t           | t            | f              | t            | f              | t          | f       
     | t          | t         | f              | 1 2    | 0 0          | 1981 1978 | 0 0       |          | 
       2833 |     2832 |        2 | t           | t            | f              | t            | f              | t          | f       
     | t          | t         | f              | 1 2    | 0 0          | 1981 1978 | 0 0       |          | 
       2835 |     2834 |        2 | t           | t            | f              | t            | f              | t          | f       
     | t          | t         | f              | 1 2    | 0 0          | 1981 1978 | 0 0       |          | 
       2837 |     2836 |        2 | t           | t            | f              | t            | f              | t          | f       
     | t          | t         | f              | 1 2    | 0 0          | 1981 1978 | 0 0       |          | 
       2839 |     2838 |        2 | t           | t            | f              | t            | f              | t          | f       
     | t          | t         | f              | 1 2    | 0 0          | 1981 1978 | 0 0       |          | 
(5 rows)

postgres=# select * from pg_class where oid =2831;
       relname       | relnamespace | reltype | reloftype | relowner | relam | relfilenode | reltablespace | relpages | reltuples | rel
allvisible | reltoastrelid | relhasindex | relisshared | relpersistence | relkind | relnatts | relchecks | relhasoids | relhaspkey | re
lhasrules | relhastriggers | relhassubclass | relrowsecurity | relforcerowsecurity | relispopulated | relreplident | relispartition | r
elhasextent | relpartkind | relparent | relfrozenxid | relminmxid | relacl | reloptions | relpartbound 
---------------------+--------------+---------+-----------+----------+-------+-------------+---------------+----------+-----------+----
-----------+---------------+-------------+-------------+----------------+---------+----------+-----------+------------+------------+---
----------+----------------+----------------+----------------+---------------------+----------------+--------------+----------------+--
------------+-------------+-----------+--------------+------------+--------+------------+--------------
 pg_toast_2604_index |           99 |       0 |         0 |       10 |   403 |        2831 |             0 |        1 |         0 |    
         0 |             0 | f           | f           | p              | i       |        2 |         0 | f          | f          | f 
          | f              | f              | f              | f                   | t              | n            | f              | f
            |             |         0 |            0 |          0 |        |            | 
(1 row)
主要系统表:
  1. 系统表 pg_namespace 用于存储命名空间
  2. pg_tablespace 存储表空间信息,将表放置在不同的表空间有助于实施磁盘文件布局。
  3. pg_database中存放 了当前数据集簇中数据库的信息,它也是一个在整个集簇范围内共亭的系统表.
  4. pg_c1ass 存储表及与表类似结构的数据库对象信息,包含索引、序列、视图、复合数据类型、 TOAST 表等。
  5. pg_type存储数据类型信息
  6. pg_attribute 存储表的属性信息,对于数据库中表的每个属性都有一个元组。
  7. pg_index 存储索引的具体信息

关键系统表之间的相互依赖关系

pg_namespace表中的命名空间OID 与pg_class表中的命名空间OID相关联,用于确定表所属的命名空间。
pg_class表中的表OID与pg_attribute表中的表OID相关联,用于确定每个表的列信息。
pg_class表中的表OID与pg_index表中的表OID相关联,用于确定每个表的索引信息。

系统表初始化

在PostgreSQL数据库安装完后,需要先进行初始化数据库操作(initdb),生成模板数据库和相应的目录、文件信息,系统表即在此阶段生成。而用户数据库及其系统表都是从模板数据库进行复制生成的。

系统表的访问

由于系统表保存了数据库的所有元数据,所以系统运行时对系统表的访问是非常频繁的。为了 提高系统性能,在内存中建立了共享的系统表CACHE,使用 Hash 函数和 Hash 表提高查询效率。
PG 的 cache 体系 主要有三种:
  • syscache,缓存系统表数据
  • relcache,缓存 一个表关系relation 的完整数据(包括用户表的)
  • plancache,缓存planstmt,加速一个query对同一个planstmt 的访问
代码路径:src/backend/utils/cache/
主要介绍的是前两种:

SysCache(CatCache

SysCache介绍

代码路径:src/include/utils/catcache.h
  • 缓存系统表数据,也叫 CatCache catalog cache,catcache中存放最近访问过的系统表缓存。
  • 从实现上看SysCache就是一个数组,数组的长度为预定义的系统表的个数。在PG 8.4.1中实现了54个系统表,因此SysCache数组具有54个元素。每个元素的数据结构为CatCache,该结构体内使用Hash来存储被缓存的系统表元组,每个系统表唯一地对应一个SysCache数组中的CatCache结构。
  • 在Postgres进程初始化时(在InitProgres中),将会对SysCache进行初始化。SysCache的初始化实际上是填充SysCache数组中每个元素的CatCache结构的过程,主要任务是将査找系统表元组的关键字信息写入SysCache数组元素中。这样通过指定的关键字可以快速定位到系统表元组的存储位置。
  • CatCache的数据结构如下:
  • 在SysCache.c文件中已经将所有系统表的CatCache信息存储在一个名为cacheinfo的静态数组 中,每个系统表的CatCache信息用一个数组元素来描述,其数据类型为cachedesc:
 
struct cachedesc
{
        Oid                        reloid;                        /* OID of the relation being cached */
        Oid                        indoid;                        /* OID of index relation for this cache */
        int                        nkeys;                        /* # of keys needed for cache lookup */
        int                        key[4];                        /* attribute numbers of key attrs */
        int                        nbuckets;                /* number of hash buckets for this cache */
};
为了便于查找, SysCache 中的 CatCache 通过其 cc_next 字段构成了一个单向链表,其头部用全
局变量CacheHdr 记录(数据结构为catcacheheader):
 
typedef struct catcacheheader
{
    slist_head    ch_caches;        //指向 CatCache 链表的头部
    int            ch_ntup;        //所有CatCache中缓存元组的总数
} CatCacheHeader;

SysCache初始化

  • 第一阶段:PG 在初始化 backend进程时 会通过 InitPostgres --> InitCatalogCache 完成对 SysCache 的初始化,并建立由CacheHdr记录的CatCache链表, 这里 SysCache 是一个 CatCache 结构的数组。InitCatalogCache函数中对SysCache的初始化主要分为以下几个步骤:
    • 根据cacheinfo为SysCache数组分配空间,这里将SysCache的长度设置为和cacheinfo数组相同。
    • 循环调用InitCatcache函数根据cacheinfo中的每一个元素生成CatCache结构并放入SysCache数组的对应位置中。InitCatcache每调用一次将处理一个cachedesc结构。该函数根据cachedesc中要求的Hash桶的数量 为即将建立的CatCache结构分配内存,并根据cachedesc结构中的信息填充CatCache的各个字段。 最后将生成的CatCache链接在CacheHdr所指向的链表的头部。
  • 第二阶段:调用函数RelationCacheInitializePhase2(负责RelCache的初始化)进行第二阶段初始化,该函数调用InitCatcachePhase2。InitCatcachePhase2将依次完善SysCache数组中的CatCache结构,主要是根据对应的系统表填充CatCache结构中的元组描述符(cc_tupledesc)、系统表名(cc_relname)、查找关键字的相关字段(cc_hashfunc、cc_isname、cc_skey)等。
  • SysCache数组初始化后,CatCache内是没有任何元组的,但是随着系统运行,对于系统表元组的访问,其中的系统表元组也会逐渐增多。

RelCache

RelCache 介绍

  • RelCache 中包含所有最近访问过的表的模式信息(包含系统表的信息) 。RelCache 的管理比 SysCache 要简单许多,原因在于大多数时候 RelCache 中存储的 RelationData 的结构是不变的,因此 PG仅用一个 Hash 表管理RelCache 。对 RelCache 的查找、 插入、删除、修改等操作也非常简单。当需要打开一个表时,首先在 RelCache 中寻找该表的 RelationData 结构,如果没有找到,则创建该结构并加入到 RelCache 中。
  • RelCache 中存放的不是元组,而是 RelationData 数据结构:

RelCache初始化

与SysCache 的初始化类似, RelCache 的初始化同样也在 lnitPostgres 函数中进行,同样分为两个阶段: RelationCachelnitialize 和RelationCacheInitializePhase2。
  • 第一阶段:InitPostgres会调用函数RelationCachelnitialize对ReiCache进行第一阶段初始化,该函数将为该进程创建一个Hash表,其Hash键为表的OID,并设置Hash函数为oid_hash。Hash表的创建在函数hash_create中实现,该函数将创建一个标准Hash表结构体HTAB。
  • 第二阶段:在完成了 Hash表的创建后,InitPostgres将调用RelationCachelnitializePhase进人第二阶段的初始化。该函数将必要的系统表和系统表索引的模式信息加人到RelCache中,这个过程通过函数RelationCacheInitializePhase2 来实现。这个阶段会确保 pg_class、pg_attribute、pg_proc、pg_type 四个系统表及相关索引的模式信息被加入到RelCache。在PostgreSQL中,使用一个文件pgJntemaLinit来记录系统表RelationData结构体,若该文件存在且未损坏,则将其内容直接读人RelCache中。否则,分别建立 pg_class、pg__atlribute、pg_proc、pg_type 及其索引的 RelationData 结构,加入到 RelCache上的Hash表中,并重写pg_internal.init文件。
  • 当RelCache初始化完成后,我们就可以使用它来査找表的模式信息。RelCache的主要操作包括:

RelCache的主要操作

插人新打开的表
当打开新的表时,要把它的RelationData加入到RelCache中。该操作通过宏RelationCachelnsert来实现:首先,根据关系表OID在Hash表中找到对应的位置,调用函数hash_search,指定査询模式为HASH_ENTER,该模式下若发现OID对应的Hash桶已存在,则返回其指针;否则创建一个新的空的hash桶,返回其指针。然后将返回的指针强制转换为RelIdCacheEnt(数据结构:relidcacheent),然后把打开表的RelationData陚值给reldesc字段。
 
typedef struct relidcacheent
{
    Oid                        reloid;
        Relation        reldesc;
} RelIdCacheEnt;
 
査找Hash表
査找Hash表通过定义宏RelationldCacheLookup (ID,RELATION)来实现,调用函数hash_search,指定査询模式为HASH_FIND,若找到ID对应的RelldCacheEnt,则将其iddesc字段的值賦值给 RELATION;否则,设置RELATION为NULL。
从Hash表中删除元素
从Hash表中删除元素通过定义宏RelationCacheDelete(RELATION)来实现,调用函数hash_search,指定査询模式为HASH_REVOKE,在该模式下,若找到对应的Hash桶,则将其删除;否则返回NULL。
 
Hash表实际上扮演了 RelCache索引的角色,所有对于RelCache的査找都是通过Hash表辅助进行的。
 
文章来自个人专栏
数据库pg
4 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0