1.PAGE
1.1 page 结构
每个page(也叫block)包含page header、Iterm Pointers、free space、tuples、special space 。
* +----------------+---------------------------------+
* | PageHeaderData | linp1 linp2 linp3 ... |
* +-----------+----+---------------------------------+
* | ... linpN | |
* +-----------+--------------------------------------+
* | ^ pd_lower |
* | |
* | v pd_upper |
* +-------------+------------------------------------+
* | | tupleN ... |
* +-------------+------------------+-----------------+
* | ... tuple3 tuple2 tuple1 | "special space" |
* +--------------------------------+-----------------+
* ^ pd_special
上面就是/soft/postgresql-14.6/src/include/storage/bufpage.h 中给出的page页结构图,我们也可以使用pageinspect插件查看PostgreSQL中的页结构
postgres=# create extension pageinspect; --创建pageinspect扩展
CREATE EXTENSION
postgres=# create table t1(id int,name varchar(10));
CREATE TABLE
postgres=# SELECT lower, upper, special, pagesize
postgres-# FROM page_header(get_raw_page('t1',0));
ERROR: block number 0 is out of range for relation "t1" --未插入数据,此时没有分配page
postgres=# insert into t1 values(1,'aaa');
INSERT 0 1
postgres=# commit;
postgres=# SELECT lower, upper, special, pagesize
postgres-# FROM page_header(get_raw_page('t1',0));
lower | upper | special | pagesize
-------+-------+---------+----------
28 | 8160 | 8192 | 8192
(1 row)
根据上面查询,可以大致知道t1表的第一个块的结构:
- 0-23 位,24字节 pgheader
- 24-27 位 4 字节 item pointer 1,也成linp1 就是第一个tuple(行)指针
- 28位(低位)到8160位 (高位)是空闲空间。
- 8161到8192为第一个tuple位置,也就是行记录,一行占32个字节
8192标记为特殊空间,它可以包含访问方法想存放的任何东西。比如,b-tree 索引用它存储指向页面的左右兄妹的链接,以及其他一些和索引结构相关的数据。普通表并不使用这个部分。
如果我们再查如一行记录,lower应该变为32,upper应该变为8128,下面验证
postgres=# insert into t1 values(2,'bbb');
INSERT 0 1
postgres=# SELECT lower, upper, special, pagesize
FROM page_header(get_raw_page('t1',0));
lower | upper | special | pagesize
-------+-------+---------+----------
32 | 8128 | 8192 | 8192
(1 row)
1.2 PAGE HEADER
Page Header的结构在bufpage.h的PageHeaderData结构体定义,主要包含以下内容:
- pd_lsn(8位):记录此页面的最后修改的xlog 记录
- pd_checksum(2位):页面校验码
- pd_flag(2):标志位
- pd_lower(2):空闲空间开头偏移量
- pd_upper(2):空闲空间结尾偏移量
- pd_special(2):特殊空间开头偏移量
- pd_pagesize_version(2):页面大小和版本号
- pd_prune_xid(4):页面最老未删除的xmax,如果没有则为0
上述信息都可以通过pageinspect查看到
postgres=# SELECT * FROM page_header(get_raw_page('t1',0));
lsn | checksum | flags | lower | upper | special | pagesize | version | prune_xid
-----------+----------+-------+-------+-------+---------+----------+---------+-----------
0/308BB20 | 0 | 0 | 32 | 8128 | 8192 | 8192 | 4 | 0
(1 row)
2.TUPLE
2.1 结构
所有的的表的tuple结构都是一致的。包含定长的header(一般为23字节),可选的空值位图,可选的对象ID域和用户数据。
tuple header的结构如下:
t_xmin(4):插入xid标志,在创建(insert)记录(tuple)时,记录此值为插入tuple的事务ID
t_xmax(4):删除xid标志,默认值为0,在删除tuple时,记录此值
t_cid(4):插入和删除cid标志(覆盖t_xvac)
t_xvac(4):vacuum操作移动一个行版本的xid
t_ctid(6):当前版本的TID或者指向更新的行版本
t_infomask2(2):一些属性,加上多个标志位
t_infomask(2):多个标志位
t_hoff(1):到用户数据的偏移量
我们可以通过heap_page_items函数获取上面信息:
postgres=# drop table t1;
DROP TABLE
postgres=# create table t1 (id int,name varchar(10));
CREATE TABLE
postgres=# begin; --第一个事务
BEGIN
postgres=*# select txid_current();
txid_current
--------------
1048600
(1 row)
postgres=*# insert into t1 values(1,'aaa');
INSERT 0 1
postgres=*# insert into t1 values(2,'bbb');
INSERT 0 1
postgres=*# commit;
COMMIT
postgres=# begin; --第二个事务
BEGIN
postgres=*# select txid_current();
txid_current
--------------
1048601
(1 row)
postgres=*# insert into t1 values(3,'ccc');
INSERT 0 1
postgres=*# insert into t1 values(4,'ddd');
INSERT 0 1
postgres=*# insert into t1 values(5,'eee');
INSERT 0 1
postgres=*# commit;
COMMIT
postgres=# select lp as linpn,
postgres-# lp_flags,t_xmin,t_xmax,
postgres-# t_field3 as t_cid,
postgres-# t_ctid,t_infomask2,t_infomask,t_hoff
postgres-# from heap_page_items(get_raw_page('t1',0));
linpn | lp_flags | t_xmin | t_xmax | t_cid | t_ctid | t_infomask2 | t_infomask | t_hoff
-------+----------+---------+--------+-------+--------+-------------+------------+--------
1 | 1 | 1048600 | 0 | 0 | (0,1) | 2 | 2050 | 24
2 | 1 | 1048600 | 0 | 1 | (0,2) | 2 | 2050 | 24
3 | 1 | 1048601 | 0 | 0 | (0,3) | 2 | 2050 | 24
4 | 1 | 1048601 | 0 | 1 | (0,4) | 2 | 2050 | 24
5 | 1 | 1048601 | 0 | 2 | (0,5) | 2 | 2050 | 24
(5 rows)
- lp代表元组的序号。
- lp_flags ,代表元组的状态,0-unused,1-normal,2-redirect,4-dead。
- t_xmin为1048600,代表插入此元组的事务。
- t_xmax为 0 代表此事务尚未被删除或更新;
- t_cid代表是元组为1048600第一个SQL命令插入的;t_ctid为(0,1)指向自身,因为该元组为最新版本。前面的0为page序号,后面的1为偏移量(offset)取自tuple序号。
- t_infomask2
- t_infomask :t_infomask & 256 >0 xmin_committed ,t_infomask & 512>0 xmin_aborted,t_infomask & 1024 >0 xmax_committed ,t_infomask & 2048 xmax_aborted
- t_hoff代表用户数据的偏移量.
上面只描述了普通表元组结构,Index 元组和TOAST元组将在后续文章中介绍