PG INTERNAL-Database Cluster, Databases, and Tables
翻译http://www.interdb.jp/pg/pgsql01.html
这一章和下一个章节总结Postgresql的基础知识,帮助读者理解后续章节.这一章,描述了下面的主题:
- database cluster的逻辑结构
- database cluster的物理结构
- heap table文件的内部逻辑布局
- 表的读写数据的方法
1.1 database cluster的逻辑结构
一个database cluster是一组被Postgresql服务器管理的database.如果你是第一次听说这个定义,那么你可能会对它产生好奇,但是database cluster这个术语在Postgresql中不是表示一组database服务器.一个Postgresql服务器运行在一个主机(host)上并且管理着一个database cluster.
下面的图显示了一个database cluster的逻辑结构.一个database是database对象的一个集合.在关系数据库理论中,一个database对象是用于存储或者引用数据的数据结构.一个(heap) table是数据库对象的典型例子,其他的比如index,sequence,view,function等都是数据库对象.在Postgresql中,数据库本身也是数据库对象,在逻辑上是相互分隔的.其他所有数据库对象都属于他们对应的数据库.
在pg中所有的数据库对象内部都有对应的object identifiers(OIDS).它是4字节无符号整数.数据库对象之间的关系和对应的OIDS都保存在合适的相应的对象类型的system catalog
.比如,database和heap table的OIDS保存在pg_database和pg_class中,所以可以通过下面的查询找出你想要的OIDS:
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)
1.2 database cluster的物理结构
一个database cluster基本上就是作为基础目录(base directory)的一个目录.它包含了许多子目录和文件.如果你执行initdb初始化一个新的database cluster,在指定的目录下一个基础目录就会被创建.虽然不是必须的,但是通常会把基础目录的路径设置为环境变量PGDATA.
下图显示了pg中一个database cluster的例子.一个database是base子目录下的一个子目录.每一个表和index在database子目录中保存在(至少)一个文件.另外,也有其他子目录保存一些特别的数据,和配置文件.由于pg支持tablespace,这表面tablespace这一术语与其他数据库的意思是不一样的.一个tablespace在pg中实际是一个不在base子目录中的目录.
1.2.1 database cluster的布局
官方文档已经描述了database cluster的布局.下面列出主要的文件和子目录:
1.2.2 database的布局
数据库目录是在base子目录下的.database的目录名字与对应的OIDS相等.比如,sampledb的OIDS是16384,那么它的子目录名字也就是16384.
1.2.3 table和index关联的文件布局
每个大小小于1GB的表和index都保存在对应数据库子目录下的一个单独的文件中.table和index作为数据库对象内部都通过单独的OIDS进行管理,同时这些数据文件也由relfilenode变量管理.table和index的relfilenode的值基本上就是OIDS,但并不总是会相同.
table和index的relfilenode的值会被一些命令改变(TRUNCATE,REINDEX,CLUSTER).比如,如果我们truncate表sampletbl.Pg会分配一个新的relfilenode,删除旧的数据文件,创建一个新的.
在9.0以上,内建的函数pg_relation_filepath可用于根据OID或名字返回关系的文件路径.
当文件大小超过1GB,PG会创建一个新的文件命名的格式是relfilenode.1
.如果新的文件也填满了,后面会继续创建relfilenode.2
,一直按照这种方式处理.
table和index文件的最大文件大小可以,在构建postgresql的时候通过参数
--with-segsize
改变.
仔细看数据库子目录下的文件,你会发现每个表都有两个关联的文件都分别带有_fsm
和_vm
.它们是作为free space map和visibility map的引用,分别保存表每个page的free space capacity和可见性的信息.indexes只有free space maps没有visibility map.
1.2.4 Tablespace
tablespace在PG中是在base子目录外的附加的数据区域.这个功能在8.0中实现.
使用CREATE TABLESPACE会创建对应的tablespace.在对应的目录下,指定版本的子目录也就创建(比如,PG_9.4_201409291).命名方式如下:
PG _ 'Major version' _ 'Catalogue version number'
比如,如果你创建一个new_tblspc
的tablespace,路径为/home/postgres/tblspc
,oid是16386,那么子目录PG_9.4_201409291
会在tablespace中创建.
tablespace的目录会被pg_tblspc
下的子目录软连接过去,连接的名字等于tablespace的OID.
如果你在tablespace下创建一个新的database(OID是16387),它的子目录创建在指定版本子目录(version-specific subdirectory)下.
如果在base目录下的一个已创建的数据库下创建一个新的表,使用tablespace.那么第一步,与已存在的数据库的OID相同名字的新目录会在version specific subdirectory下创建,然后再在该目录下创建表的文件.
1.3 Heap table file的内部布局
在数据文件内部(heap table和index,free space map与visibility map),都会被分割为固定长度的pages(blocks).pg默认是8KB.每个文件的page安装从0开始的序列编号,这叫做block numbers.如果文件被填满,pg会在文件的结尾添加一个空page,填充剩下的空间.
page的内部布局根据数据文件的类型有所不同.
表中的page包含一下三种数据:
- heap tuple:heap tuple记录数据本身.它们是从page底部开始的栈结构.
- line pointer(s):一个linepointer是4字节长度,保存指向heap tuple的指针,也称为item pointer.
- header data:header data由数据结构PageHeaderData定义,在page的开始的地方.它是24字节长度,包含了关于page的通用信息.
- pd_lsn:这个变量记录最后修改page的XLOG记录的LSN.是8字节无符号整数,与WAL机制有关.
- pd_cjecksum:保存整个page的校验码(注意这个变量只存在9.3版本以上,之前的版本这部分数据保存在page的timelineId中).
- pd_lower,pd_upper:pd_lower指向line pointers的结尾,pd_upper指向最新的heap tuple的开头.
- pd_special:这个变量用于索引.在table中,它指向page的结尾.(在index的page中,它指向一个特殊空间的开头,这个特殊空间保存不同索引类型的特殊数据,比如BTREE,GiST等是不同的).
在line pointer的结尾与最新tuple的开头之间的空间被应用为free space或者hole.
定位一个table中的tuple,内部使用tuple identifier(TID).一个TID由一对数据组成:page的block number和指向tuple的line pointer的偏移数.
PageHeaderData定义在 src/include/storage/bufpage.h
另外,如果heap tuple的大小大于2kb(1/4 of 8 kb),使用 TOAST (The Oversized-Attribute Storage Technique) 方法保存和管理.具体内容可查看官方文档.
1.4 读写tuple的方法
1.4.1 写heap tuple
假设一个表包含一个page,这个page只有一个heap tuple.pd_lower指向第一个line pointer,这个line ponter和pd_upper都指向第一个tuple.
当第二个tuple插入时候,它被放在第一个的后面.第二个line pointer会被push到第一个后面,然后指向第二个tuple.pd_lower改变,并指向第二个line pointer.pd_upper指向第二个tuple.其他的header data(比如pd_lsn,pd_checksum,pd_flag等)都会改变.
1.4.2 读Heap tuple
在这里描述两种典型的access method,sequential scan和btree index scan.
- **sequentialscan: 所有page中的所有tuple都是通过扫描所有line pointers顺序读取的.
- B-tree index scan:一个index文件包含index tuple,每一个index tuple由index key和指向目标heap tuple的TID.如果相应查找的key的index tuple被找到,pg使用TID读取对应的heap tuple.比如,TID的值为(block=7,offset=2),表明目标的heap tuple是第7个page的第二个heap tuple.所以pg就不需要扫描page来读取对应的heap tuple.
Postgresql也支持TID-scan,Bitmap-Scan,和Index-Only-Scan.
TID-Scan是直接通过TID访问tuple的方法.比如,查找第0个page的第一个tuple,可以使用下面的查询