Postgresql 逻辑结构和物理结构

1.数据库的逻辑结构

Database cluster: 由postgresql server管理的数据库的集合,下面由多个database组成

databsase: 由各种数据库对象构成,比如下图中的table,indexes, view,function,sequence…

所有数据库对象都有各自的oid(object identifiers),oid是一个无符号的四字节整数,相关对象的oid都存放在相关的system catalog表中,比如数据库的oid和表的oid分别存放在pg_database,pg_class表中。

如下所示:

sampledb=# SELECT datname, oid FROM pg_database WHERE datname = 'sampledb'; datname  |  oid  ----------+------- sampledb | 16384(1 row) sampledb=# SELECT relname, oid FROM pg_class WHERE relname = 'sampletbl';  relname  |  oid  -----------+------- sampletbl | 18740 (1 row)

2.数据库的物理结构

在执行initdb的时候会初始化一个目录,通常我们都会在系统配置相关的环境变量$PGDATA来表示,初始化完成后,会再这个目录生成相关的子目录以及一些文件。在postgresql中,tablespace的概念并不同于其他关系型数据库,这里一个tablespace对应的都是一个目录。如下图就是PG的物理结构

文件和目录相关作用描述

filesdescription
PG_VERSION包含postgresql主版本号的文件
pg_hba.conf控制postgresql客户端验证的文件
pg_ident.conf控制postgresql用户名映射的文件
postgresql.conf配置参数文件
postgresql.auto.conf用于存储在ALTER SYSTEM(版本9.4或更高版本)中设置的配置参数的文件
postmaster.opts记录服务端上一次启动的命令行选项
subdirectoriesdescription
base/包含每个数据库子目录的子目录
global/包含群集范围表的子目录,例如pg_database和pg_control
pg_commit_ts/包含事务提交时间戳数据的子目录。 9.5版本以后
pg_clog/ (Version 9.6 or earlier)包含事务提交状态数据的子目录。它在版本10中重命名为pg_xact.  CLOG将在5.4章节中详解。.
pg_dynshmem/包含动态共享内存子系统使用的文件的子目录。9.4版本以后
pg_logical/包含逻辑解码的状态数据的子目录。9.4版本以后
pg_multixact/包含多事务状态数据的子目录(用于 shared row locks)
pg_notify/包含LISTEN / NOTIFY状态数据的子目录
pg_repslot/包含复制槽数据的子目录(9.1版本以后)
pg_serial/包含有关已提交的序列化事务(9.1版本以后)信息的子目录
pg_snapshots/包含导出快照的子目录(9.2版本以后)。 PostgreSQL的函数pg_export_snapshot在此子目录中创建快照信息文件
pg_stat/包含统计子系统永久文件的子目录
pg_stat_tmp/包含统计子系统临时文件的子目录
pg_subtrans/包含子事物状态数据的子目录
pg_tblspc/表空间符号链接目录
pg_twophase/包含prepare事务的状态文件
pg_wal/ (Version 10 or later)包含WAL(Write Ahead Logging)段文件的子目录。在版本10中从pg_xlog重命名而来.
pg_xact/ (Version 10 or later)包含事务提交状态数据的子目录。在版本10中从pg_clog重命名而来.CLOG将在5.4章节中详解
pg_xlog/ (Version 9.6 or earlier)包含WAL(Write Ahead Logging)段文件的子目录。在版本10中重命名为pg_wal

2.1 database的物理布局设计

  每个数据库都会在$PGDATA/base下面生成一个子目录,如下图,都会一一对应。

postgres@db-192-168-101-115-> pwd/opt/pgdata9.6/pg_root/basepostgres@db-192-168-101-115-> lltotal 1.1Mdrwx------ 2 postgres postgres 4.0K Aug 29  2017 1drwx------ 2 postgres postgres 4.0K Aug 29  2017 13268drwx------ 2 postgres postgres 976K May 14 11:36 13269drwx------ 2 postgres postgres  12K May 14 11:36 137745drwx------ 2 postgres postgres 4.0K Aug 29  2017 137915drwx------ 2 postgres postgres  20K Aug 29  2017 137943drwx------ 2 postgres postgres  16K Aug 29  2017 137945drwx------ 2 postgres postgres  12K Aug 29  2017 137947drwx------ 2 postgres postgres 4.0K Aug 29  2017 139298drwx------ 2 postgres postgres  20K May 14 11:36 139312drwx------ 2 postgres postgres  12K Aug 29  2017 17220drwx------ 2 postgres postgres 4.0K Aug 29  2017 17289drwx------ 2 postgres postgres 4.0K Aug 29  2017 177374drwx------ 2 postgres postgres 4.0K Jul 10  2017 pgsql_tmp
hank=> select datname,oid from pg_database;   datname  |  oid   -----------+-------- postgres  |  13269 template1 |      1 template0 |  13268 hank      |  16395 rmt_db    |  17220 loc_db    |  17289 pgcluster | 137745 swrd      | 137915 scm       | 137943 hive      | 137945 oozie     | 137947 sqoop     | 139298 hue       | 139312 zabbix    | 177374(14 rows)

2.2 表和索引的物理布局设计

每一个表和索引如果不超过1G大小,都只有一个文件。表和索引也有和数据库一样的OID,另外还有一个relfilenode,这个值不会总是匹配OID,在发生一truncate,reindex,cluster等相关的操作,会发生变化,见如下示例:

可以看到开始oid和relfilenode是一样的,truncate后,relfilenode发生了变化.

hank=> SELECT relname, oid, relfilenode FROM pg_class WHERE relname = 'a';    relname |  oid   | relfilenode ---------+--------+------------- a       | 186884 |      186884  postgres@db-192-168-101-115-> ll -a /opt/pgdata9.6/pg_root/pg_tblspc/16393/PG_9.6_201608131/16395/186884-rw------- 1 postgres postgres 1.5M Jan 16 17:11 /opt/pgdata9.6/pg_root/pg_tblspc/16393/PG_9.6_201608131/16395/186884  hank=> truncate table a;TRUNCATE TABLEhank=> SELECT relname, oid, relfilenode FROM pg_class WHERE relname = 'a'; relname |  oid   | relfilenode ---------+--------+------------- a       | 186884 |      187892(1 row) hank=> SELECT pg_relation_filepath('a');               pg_relation_filepath              ----------------------------------------------- pg_tblspc/16393/PG_9.6_201608131/16395/187892

如果数据数据文件超过1GB,那么就会新生成一个文件,如下:

 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

注意:表和索引的文件大小的限制可以在编译的时候通过–with-segsize设置

3.表空间物理结构

在PG中,除了base目录,自己新建的tablespace对应的目录都会再pg_tblspc下,如下图

比如新建一个tbs_hank表空间,目录是/data02/pgdata/tbs_hankcreate tablespace tbs_hank owner postgres location '/data02/pgdata/tbs_hank'; 那么就会再pg_tblspc下面有一个软连接的目录,这里16393就是这个tablespace的OIDpostgres@db-192-168-101-115-> pwd/opt/pgdata9.6/pg_root/pg_tblspcpostgres@db-192-168-101-115-> lltotal 0lrwxrwxrwx 1 postgres postgres 23 Oct 10  2016 16393 -> /data02/pgdata/tbs_hank 再往下,就可以看到16395,是database的OIDpostgres@db-192-168-101-115-> pwd/opt/pgdata9.6/pg_root/pg_tblspc/16393/PG_9.6_201608131postgres@db-192-168-101-115-> ls16395  pgsql_tmp

4.堆表文件的内部结构

在表对应的datafile中,被分离为固定大小的page(or block),默认为8KB,这些page在datafile中从0开始计数,如果一个page被填充满,那么就会生成新的page以添加到文件,所以我们看到的datafile会随着表的增大,也在不断增大。

一个堆表数据文件的内部结构设计如下图:

一个表的page包括三种类型的数据

1.heap tuple: 存放数据本身,从一个page的末端有序的堆积。

2.line pointer: 一个四字节的行指针,指向每一个heap tuple,也叫item pointer,line pointer是一个简单的数组,索引page的数据文件是从1开始计数,也叫offset number,新的tuple增加到page时,line piniter就在推送到数组中,指向新的tuple.

3.header data:  header data是page生成的时候随之产生的,由pageHeaderData定义结构,24个字节长,包含了page的一般信息,主要结构描述如下:

           pd_lsn: 存储XLOG最后的改变的这个page的LSN号,是一个8字节的无符号整数,和WAL相关,后续章节会有描述

           pd_checksum:存储page的校验和

           pd_lower,pd_upper:  pd_lower指向line pointer的尾部,pd_upper指向最新heap tuple的开头

           pd_special: 此变量用于索引,在表的page中,它指向page的末尾(在索引的page中,它指向特殊空间的开头)

5.读写tuple的方法

5.1写head tuple

假设我们我们的表只有一个page,这个page里只有一个tuple,如下图,pd_lower指向line pointer尾部,pd_upper指向tuple1的头部,当tuple2插入后,2号line pointer指向tuple2的头部,pd_lower指向了2号line pointer的末尾,pd_upper指向了tuple2的头部,其他的数据(pg_lsn,pg_flags等等)也会适当的被重写。后面章节会详解

5.2 读heap tuple

两种典型的访问方法,顺序扫描和B-tree索引扫描

a. sequential  scan:表中的所有page中的所有tuple通过每个page中的所有line pointer依次读取

b. B-tree index can: 每个索引文件都包含index tuple,每个index tuple都是由索引键和一个指向目标heap tuple的point构成的TID所构成,如果索引的键值被找到,那么就从index tuple中获取TID的值去找想要的数据。如以下示例:通过索引的键值Queen,在index tuple中找到对应的TID(block=7,Offset=2),这里的意思就是第七个page的第二个tuple.因此PG不需要在page中进行没有必要的扫描。

索引内部原理

这一章节不介绍索引,为了明白索引工作原理,建议阅读以下帖子:


PostgreSQL 同样也支持TID-Scan, Bitmap-Scan, and Index-Only-Scan.

TID-Scan is a method that accesses a tuple directly by using TID of the desired tuple. For example, to find the 1st tuple in the 0-th page within the table, issue the following query:

TID-Scan是一种通过使用所需行的TID直接访问数据的方法。例如,要在表中找到第0个块中的第一个行,请发出以下查询:

sampledb=# SELECT ctid, data FROM sampletbl WHERE ctid = '(0,1)'; ctid  |   data    -------+----------- (0,1) | AAAAAAAAA(1 row)

Index-Only-Scan将在 Chapter 7介绍.

出至http://www.interdb.jp/pg/pgsql01.html

Leave a Reply