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的物理结构
文件和目录相关作用描述
files | description |
PG_VERSION | 包含postgresql主版本号的文件 |
pg_hba.conf | 控制postgresql客户端验证的文件 |
pg_ident.conf | 控制postgresql用户名映射的文件 |
postgresql.conf | 配置参数文件 |
postgresql.auto.conf | 用于存储在ALTER SYSTEM(版本9.4或更高版本)中设置的配置参数的文件 |
postmaster.opts | 记录服务端上一次启动的命令行选项 |
subdirectories | description |
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中进行没有必要的扫描。
索引内部原理
这一章节不介绍索引,为了明白索引工作原理,建议阅读以下帖子:
- Indexes in PostgreSQL — 1
- Indexes in PostgreSQL — 2
- Indexes in PostgreSQL — 3 (Hash)
- Indexes in PostgreSQL — 4 (Btree)
- Indexes in PostgreSQL — 5 (GiST)
- Indexes in PostgreSQL — 6 (SP-GiST)
- Indexes in PostgreSQL — 7 (GIN)
- Indexes in PostgreSQL — 9 (BRIN)
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介绍.