PostgreSQL 之 物理存储

  • 目录与文件

对于集簇里的每个数据库,在表空间目录(base或者自定义)里都有一个子目录对应,子目录的名字为该数据库在pg_database里的OID。

每个表和索引都存储在独立的文件里。对于普通关系,这些文件以表或索引的filenode命名,它可以在pg_class.relfilenode中找到。(这个filenode是pg自己内部维护的,我第一次竟然理解成了文件系统的,尴尬)。

注意:

虽然普通表的filenode通常和它的pg_class.OID相匹配,但实际上并不必须如此。

有些操作,比如TRUNCATE、REINDEX、CLUSTER以及某些形式的ALTER TABLE,都可以改变pg_class.relfilenode而同时保留pg_class.OID。(试了一下truncate,确实改变了,还发现了显式事务内部truncate能回滚的原理)

此外,对于特定系统目录,其pg_class.relfilenode包含0。这些目录的实际文件节点号被存储在一个低层数据结构pg_filenode.map中,并且可以使用pg_relation_filenode()函数获取。

PostgreSQL 物理文件映射解析

每个表和索引有一个空闲空间映射(FSM)文件,该文件以filenode加上后缀_fsm命名,它存储可用空闲空间的信息。

每个表还有一个可见性映射(VM),存储在一个后缀为_vm的文件中,它用于跟踪哪些页面已知含有非死亡元组。

不被日志记录的表和索引还有一个_init文件,即初始化文件,崩溃时,用于复制恢复,其他_fsm和_vm之类的文件会被直接擦除。

对于临时表,文件名的形式为tBBB_FFF,其中BBB是创建该文件的backend id,FFF是filenode。

表或者索引段超过segsize(编译时可配置)之后,就会被切分,后续段被命名为 filenode.1、filenode.2等等。原则上,_fsm和_vm文件也可以有多个段,但实际上很少发生。

表列中存储相当大的项,那么该表就会有个与之相关联的TOAST表,用于存储无法保留在在表行中的字段值。如果表有TOAST表,该表的pg_class.reltoastrelid链接到它的TOAST表。(TOAST部分详细介绍)。

用户自定义表空间在pg_tblspc目录里面有一个用该表空间的OID命名的符号连接,它指向物理的表空间目录。

临时文件(例如排序内存不足时产生的)被创建在 临时表空间下的pgsql_tmp目录中,临时文件的名称的形式为pgsql_tmpPPP.NNN,其中PPP是其所属后端的PID,而NNN用于区别该后端的不同临时文件。

pg_relation_filepath()函数显示任何关系的完整路径(相对于PGDATA)。

 

  • 空闲空间映射(FSM)

每一个堆和索引关系(除了哈希索引)都有一个空闲空间映射(FSM)来保持对关系中可用空间的跟踪。

PostgreSQL 之 FSM

 

  • 可见性映射 (VM)

_VM文件为每个堆页面存储两个bit。
第一bit被设置,表示该页面上的元组都是有效的。index-only scans会利用这个bit。
第二bit被设置,表示该页面上的元组都已经被冻结。防回卷清理操作也不需要重新访问该页面。
可见性映射的位只会被清理操作设置,但是可以被任何在页面上进行的数据修改操作清除。

 

  • 数据库页面布局

术语item指的是存储在一个页面里的 独立数据值。在一个表里,一个item是一个行。在一个索引里,一个item是一条索引记录。序列和TOAST表的格式就像一个普通表一样。

页面总体布局(所有细节都可以在src/include/storage/bufpage.h中找到)。

PageHeaderData  24字节长。包含关于页面的一般信息,包括空闲空间指针。
ItemIdData      存放(偏移量,长度)对的数组,指向实际Item。每个4字节。一个Item会因为压缩空闲空间在页面内进行移动,但对应的ItemIdData不会移动。实际上,每个指向Item的指针(ItemPointer,也叫做CTID)都由一个blocknumber和一个ItemIdData的索引组成。
Free space      未分配的空间(空闲空间)。新Item指针从这个区域的开头开始分配,新Item从其结尾开始分配。
Items           实际的Item本身。
Special space   索引访问模式相关的数据。不同的索引访问方式存放不同的数据。比如,b-tree 索引用它存储指向页面的左右兄妹的链接,以及其他一些和索引结构相关的数据。普通表并不使用这个部分(通过设置pd_special等于页面大小来做到)。

PageHeaderData布局

pd_lsn              PageXLogRecPtr  8 bytes     LSN: 最后修改这个页面的 WAL 记录最后一个字节后面的第一个字节
pd_checksum         uint16          2 bytes     页面校验码(当data checksums被设置,这个域是有效的,否则可以忽略)
pd_flags            uint16          2 bytes     标志位,标识页面存储情况
pd_lower            LocationIndex   2 bytes     到空闲空间开头的偏移量
pd_upper            LocationIndex   2 bytes     到空闲空间结尾的偏移量
pd_special          LocationIndex   2 bytes     到特殊空间开头的偏移量
pd_pagesize_version uint16          2 bytes     页面大小和布局版本号信息
pd_prune_xid        TransactionId   4 bytes     页面上最老未删除XMAX,如果没有则为0

HeapTupleHeaderData布局(表和序列都使用的Item结构的行头,所有细节都可以在src/include/access/htup_details.h中找到)。

t_xmin              TransactionId   4 bytes     插入XID标志
t_xmax              TransactionId   4 bytes     删除XID标志
t_cid               CommandId       4 bytes     插入和/或删除CID标志(覆盖t_xvac)
t_xvac              TransactionId   4 bytes     VACUUM操作移动一个行版本的XID
t_ctid              ItemPointerData 6 bytes     当前版本的CTID或者指向更新的行版本
t_infomask2         uint16          2 bytes     一些属性,加上多个标志位
t_infomask          uint16          2 bytes     多个标志位
t_hoff              uint8           1 byte      到用户数据的偏移量

数据的存储

HeapTupleHeaderData
空值位图(可能没有)
对齐填充
OID(可能没有)
其他字段,在行内的具体位置,依靠pg_attribute中,attlen和attalign确定

PostgreSQL中page页结构

 

  • TOAST(超尺寸属性存储技术)

PostgreSQL使用固定的页面尺寸(通常是8kB),并且不允许元组跨越多个页面。因此不可能直接存储非常大的字段值。为了克服这个限制,大的字段值会被压缩并/或分解成多个物理行。

只有变长(varlena)数据类型支持TOAST,通常在存储值的头四个字节,表示值的总字节数。

TOAST占用变长类型的长度字的最高两个2个bit,也就是剩下的30位可以用来表示大小,1GB。

1XXXXXXX                               变长类型值简短存储。剩余7bit表示(正常值+长度字)有多少字节。适合127字节以内的值。(大多数变长数据)
00XXXXXX XXXXXXXX XXXXXXXX XXXXXXXX    变长类型值普通存储。剩余30bit表示(正常值+长度字)有多少字节。
01XXXXXX XXXXXXXX XXXXXXXX XXXXXXXX    变长类型值压缩存储。剩余30bit表示(压缩值+长度字)有多少字节。
10000000 XXXXXXXX                      变长类型值线外存储。表示一个TOAST指针,真正的值在TOAST表里。第二个字节决定这个TOAST指针的类型和尺寸。

如果表包含main,extended或external存储策略的字段时,那么该表将有一个关联的TOAST表(如果可以计算出来不需要TOAST表则不会有,比如两个varchar(10)字段这种)。其OID存储在表的pg_class.reltoastrelid项中。

被TOAST过的值保存在TOAST表里。值或压缩过的值被分裂成最大为TOAST_MAX_CHUNK_SIZE(通常是blocksize的1/4)字节的块。每个块都作为独立的行存储在TOAST表中。

TOAST表有三个字段:chunk_id(被TOAST值的OID),chunk_seq(块序列号,表示在值中第几个块)和一个chunk_data(块实际数据)。在(chunk_id,chunk_seq)上有唯一索引,进行快速检索。

TOAST后原位置存储共18byte:10000000 XXXXXXXX(2byte) + 值原始尺寸(4byte) + 物理存储尺寸(4byte) + chunk_id(4byte) + TOAST表OID(4byte)。

TOAST指针结构:

struct varatt_external  
{  
    int32   va_rawsize;        /* Original data size (includes header) */  
    int32   va_extsize;        /* External saved size (doesn't) */  
    Oid     va_valueid;        /* Unique ID of value within TOAST table */  
    Oid     va_toastrelid;    /* RelID of TOAST table containing it */  
};

只有在准备向表中存储超过TOAST_TUPLE_THRESHOLD(通常是blocksize的1/4)字节的行值的时候才会触发TOAST管理代码toast_insert_or_update(),压缩或线外存储字段值,

直到行值比TOAST_TUPLE_TARGET(通常也是blocksize的1/4)字节短,或者无法得到更好的结果的时候才停止。

update时没有更新的线外TOAST值保持不动。

TOAST管理代码识别四种在磁盘上存储可TOAST列的策略:

PLAIN       避免压缩或者线外存储,而且对于变长类型禁用单字节头部。这是不可TOAST数据类型列的唯一策略,非变长类型当然也可以用。
EXTENDED    允许压缩和线外存储。这是大多数可TOAST数据类型的默认策略。先尝试压缩,如果行仍然太大,则进行线外存储。
EXTERNAL    允许线外存储,但不许压缩。将让宽text和bytea列上的操作更快(空间换时间)。
MAIN        允许压缩,但不允许线外存储(实际上,仍然会进行线外存储,但只作为没办法把行变得放入一页的最后手段)。

行存储逻辑中变长类型字段存储方式:

(1)计算TUPLESIZE(提一下127字节及以内的PLAIN策略变长字段是禁用单字节头部的,这是存储时的事情,和计算TUPLESIZE关系不大)。
(2)TUPLESIZE小于TOAST_TUPLE_THRESHOLD情况下,普通存储。 
(3)TUPLESIZE大于TOAST_TUPLE_THRESHOLD,会触发TOAST管理代码,EXTENDED策略的变长字段按顺序依次压缩并重计算TUPLESIZE判断是否小于TOAST_TUPLE_TARGET,一旦小于立刻停止,不会继续压缩后续字段。 
(4)EXTENDED策略的变长字段全部压缩后,TUPLESIZE仍然小于TOAST_TUPLE_TARGET,则需要进行线外存储。EXTENDED和EXTERNAL策略的变长字段按顺序依次将压缩后值(EXTENDED)或未压缩值(EXTERNAL)分裂成
     最大TOAST_MAX_CHUNK_SIZE大小的N个块存储到TOAST表中,并重计算TUPLESIZE判断是否小于TOAST_TUPLE_TARGET,一旦小于立刻停止,不会继续处理后续的变长类型字段。
(5)EXTENDED和EXTERNAL策略的变长字段全部线外存储后,TUPLESIZE仍然大于TOAST_TUPLE_TARGET。MAIN策略的变长字段按顺序依次压缩并重计算TUPLESIZE判断是否小于TOAST_TUPLE_TARGET,一旦小于立刻停止,不会继续压缩后续字段。
(6)MAIN策略的变长字段全部压缩后,TUPLESIZE仍然大于TOAST_TUPLE_TARGET。MAIN策略的变长字段按顺序依次进行线外存储并重计算TUPLESIZE判断是否小于TOAST_TUPLE_TARGET,一旦小于立刻停止,不会继续处理后续字段。

TOAST介绍

 

数据库物理存储

转载于:https://www.cnblogs.com/adarking/p/10627300.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值