Postgresql物理存储结构

PostgreSQL数据库目前不支持使用裸设备和块设备, 所以PostgreSQL数据库的表中的数据总是存储在一个或多个物理的数据文件中。
具体的数据文件又分为多个固定大小的数据块, 每行数据就存放在这些数据块中。

PostgreSQL中的术语

PostgreSQL中有一些术语与其他数据库中的名称不一样, 了解了这些术语的含义, 就能更好地看懂PostgreSQL中的文档。

与其他数据库不同的术语有如下几个:

  • Relation: 表示表或索引, 也就是其他数据库的Table或Index。 具体表示的是Table还是Index需要看具体情况。
  • Tuple: 表示表中的行, 在其他数据库中使用Row来表示。
  • Page: 表示在磁盘中的数据块。
  • Buffer: 表示在内存中的数据块。

数据块结构

数据块的结构如图:


数据块的大小默认是8KB, 最大是32KB, 一个数据块中存储了多行的数据。
块中的结构是先有一个块头, 后面记录了块中各个数据行的指针, 行指针是向后顺序排列的, 而实际的数据行内容是从块尾向前反向排列的。
行数据指针与行数据之间的部分就是空闲空间。

块头记录了如下信息:

  • 块的checksum值。
  • 空闲空间的起始位置和结束位置。
  • 特殊数据的超始位置。
  • 其他一些信息。

行指针是一个32bit的数字, 具体结构如下:

  • 行内容的偏移量, 占用15bit。
  • 指针的标记, 占用2bit。
  • 行内容的长度, 占用15bit。

行指针中表示行内容的偏移量是15bit, 能表示的最大偏移量是215=32768, 因此在PostgreSQL中, 块的最大大小是32768, 即32KB。

Tuple结构

在PostgreSQL数据库中, Tuple是指数据行。
行的结构如图所示:


从图中可以看出, 行的物理结构是先有一个行头, 后面跟了各项数据。
行头中记录了以下重要信息:

  • oid、 ctid、 xmin、 xmax、 cmin、 cmax、 ctid: 这些信息的含义参见《Postgresql表中系统字段详解》。
  • natts&infomask2: 字段数, 其中低11位表示这行有多少个列。 其他的位则是HOT(Heap Only Touples) 技术及行可见性的标志位。
  • infomask: 用于标识行当前的状态, 比如行是否具有OID, 是否有空属性, 共有16位, 每位都代表不同的含义。
  • hoff: 表示行头的长度。
  • bits: 是一个数组, 用于标识该行上哪些字段(列) 为空。

在MVCC专题中提到过,行上的xmin、 xmax、 cmin、 cmax和CLOG日志一起用于控制行的可见性。
每个事务在CLOG中占用两个bit, 数据库运行一段时间后, 如几年, 就可能产生上亿个事务, 最多时甚至可能达到20亿个事务, 它们使用的CLOG可能占用512MB的空间, 在这么大的CLOG中查询事务的状态, 效率可能不高, 于是PostgreSQL对查询行的可见性做了优化, 把一些可见性的信息记录在infomask字段上, 该字段的t_infomask中有以下与可见性相关的标志位:

  • #define HEAP_XMIN_COMMITTED 0x0100 /* t_xmin committed */。
  • #define HEAP_XMIN_INVALID 0x0200 /* t_xmin invalid/aborted */。
  • #define HEAP_XMAX_COMMITTED 0x0400 /* t_xmax committed */。
  • #define HEAP_XMAX_INVALID 0x0800 /* t_xmax invalid/aborted */。
  • #define HEAP_XMAX_IS_MULTI 0x1000 /* t_xmax is a MultiXactId */。

如果t_infomask中HEAP_XMIN_COMMITTED为真, 而HEAP_XMAX_INVALID为假, 则说明该行是新插入的行, 是可见的, 此时就不需要到CLOG中查询xmin和xmax的事务状态了。
而如果未设置HEAP_XMIN_COMMITTED, 并不表示该行没有提交, 而是说不知道xmin是否提交了, 需要到CLOG中去判断xmin的状态。
HEAP_XMAX_COMMITTED也是如此。
第一次插入数据时, t_infomask中的HEAP_XMIN_COMMITTED和HEAP_XMAX_INVALID并未设置, 但当事务提交后, 有用户再读取这个数据块时会通过CLOG判断出这些行的事务已提交, 会设置t_infomask中的HEAP_XMIN_COMMITTED和HEAP_XMAX_INVALID标志位。
下次再查询该行时, 直接使用t_infomask中的HEAP_XMIN_COMMITTED和HEAP_XMAX_INVALID标志位就可以判断出行的可见性了, 不再需要到CLOG中查询事务的状态。

数据块空闲空间管理

在表中的数据块中插入、 更新和删除数据会在表中产生旧版本的数据, 这些旧版本数据通过Vacuum进程的清理会在数据块中产生空闲空间。
再向表中插入数据时, 最好的办法就是继续使用这些旧数据块中的空闲空间, 如果所有的新数据都分配新的数据块, 会导致数据文件不断膨胀。

当插入新行时, 如果多个数据块中都有空闲空间, 应把数据行插到哪个有空闲空间的数据块中呢?
首先, 有空闲空间的数据块不一定能容纳下新的数据行,所以要插入一行数据时, 首先要快速找到一个数据块, 且此数据块中的空闲空间能够放下此数据行。
要完成这一操作, 要实现以下两个功能:

  • 首先是要记录每个数据块空闲空间的大小。
  • 查找时, 不能一个一个地找, 要实现快速查找。

PostgreSQL数据库使用一个名为“FSM”的文件记录每个数据块的空闲空间。 (FSM是英文“Free Space Map”的缩写)。
PostgreSQL为缩小FSM文件的大小, 只使用一个字节来记录一个数据块中的空闲空间, 很明显一个字节是无法记录空闲空间实际大小的, 该字节值实际上代表空闲空间的一个范围, 其方法如下表所示:

字节值 表示空闲空间的范围(单位:字节)
0 0~31
1 32~63
2 64~95
3 96~127
... ...
255 8164~8192

可以看到, 如果该字节值为“0”, 则表示数据块中存在的空闲空间大小的范围为0~31字节; 如果为“1”, 则表示空闲空间大小的范围为32~63字节, 然后以此类推。

在PostgreSQL 8.4之前的版本中, 使用一个全局的FSM文件来记录所有表文件的空闲空间, 但这会导致管理的复杂和低效, 所以从PostgreSQL 8.4版本之后, 对每个数据文件创建一个名为“<表oid>_fsm”的文件, 如假设一个表“test01”的OID为“25566”, 则它的FSM文件名为“25566_fsm”。

为了快速查找到满足要求的数据块, PostgreSQL使用了树型结构组织FSM文件。

FSM文件固定使用3层树型结构, 第0层和第1层为查找辅助层, 第2层中每个块的每个字节代表其对应的数据块中的空闲空间。
在第1层中, 每个块中的字节值代表其下一层(第2层) 相应的数据块中的最大值。
假设第2层的每个数据块可以填4000个字节, 则这4000个字节对应着在真正的数据文件中的4000个数据块各有多少空闲空间, 而第1层中的这个字节, 则表示第2层中对应数据块中的最大值, 也就是指对应到真正的数据文件中这4000个数据块最大的空闲空间, 同时第0层中的每个字节表示的是下一层中数据块中的最大值。
第0层只有一个数据块, 当需要判断数据块的空闲空间是否足够大时, 只需要查询第0层的这个数据块就可确定是否有合适大小的空闲空间的数据块了。

为了简化示意图, 该图中每个块只能放4个字节的数据, 其原理与实际情况下放4000个字节是一样的。
具体示意图如图:


可以看出, 第0层数据块中的每个字节的数字代表它下一层(第1层) 数据块中每个字节数字的最大值, 第1层数据块中每个字节的数字代表它下一层数据块(第2层) 中每个字节数字的最大值, 而第2层数据块(叶子节点) 中每个字节的数字则代表数据文件中数据块中的空闲空间范围。

第0层只有一个数据块, 该数据块中的第1个字节值为“123”, 表示它下层(第1层) 的第1个数据块中各字节的最大值为“123”, 同样, 第0层的数据块的第2个字节值为“192”, 表示它下一层(第1层) 的第2个数据块中各字节的最大值为“192”, 以此类推。 第1层到第2层的映射也类似。

FSM文件并不是在创建表文件时立即创建的, 而是等到需要时才会创建, 也就是执行VACUUM操作时, 或者在为了插入行第一次查询FSM文件时才创建。
下面通过示例来验证这个过程, 先建一张表, 命令如下:

osdba=# create table test01(id int, note text);
CREATE TABLE
osdba=# insert into test01 values(1,'11111');
INSERT 0 1
osdba=# select oid from pg_class where relname='test01';
oid
-------
25827
(1 row)

然后到数据目录下查看FSM文件, 命令如下:

osdba@osdba-laptop:~/pgdata/base/16384$ ls -l 25827*
-rw------- 1 osdba osdba 8192 5月 17 22:43 25827

从上面的运行结果中可以看到并没有生成FSM文件, 再做一个VACUUM操作, 命令如下:

osdba=# vacuum test01;
VACUUM

然后再到目录下查询FSM文件, 命令如下:

osdba@osdba-laptop:~/pgdata/base/16384$ ls -l 25827*
-rw------- 1 osdba osdba 8192 5月 17 22:43 25827
-rw------- 1 osdba osdba 24576 5月 17 22:44 25827_fsm
-rw------- 1 osdba osdba 8192 5月 17 22:44 25827_vm

可以看到已生成了FSM文件, 还可以看到一个名为“25827_vm”的文件, 该文件是可见性映射表文件,下面对VM文件进行说明。

可见性映射表文件

在PostgreSQL中更新、 删除行后, 数据行并不会马上从数据块中被清理掉, 而是需要等VACUUM时清理。
为了能加快VACUUM清理的速度并降低对系统I/O性能的影响, PostgreSQL在8.4.1版本之后为每个数据文件加了一个后缀为“_vm”的文件, 此文件被称为可见性映射表文件, 简称VM文件。

VM文件中为每个数据块存储了一个标志位, 用来标记数据块中是否存在需要清理的行。
有该文件后, 做VACUUM扫描此文件时, 如果发现VM文件中该数据块上的位表示该数据块没有需要清理的行, VACUUM就可以跳过对这个数据块的扫描, 从而加快VACUUM清理的速度。

VACUUM有两种方式, 一种被称为“Lazy VACUUM”, 另一种被称为“Full VACUUM”, VM文件仅在Lazy VACUUM中使用, Full VACUUM操作则需要对整个数据文件进行扫描。

?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容