怎么从零学习PostgreSQL Page结构
这篇文章主要为大家展示了“怎么从零学习PostgreSQL Page结构”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“怎么从零学习PostgreSQL Page结构”这篇文章吧。
一、Page
pg中的page和Oracle中的数据块一样,指的是数据库的块,操作系统块的整数倍个,默认是8K也就是两个操作系统块(4k的文件系统块)。这个大小在pg编译安装configure的时候通过--with-blocksize参数指定,单位是Kb。
二、Page的内部结构
2.1 page结构
2.2 PageHeaderData数据结构 (页头)
可以看到一个Page有 Pager header(页头),后面是linp(行指针),pd_lower和pd_upper分别是空闲空间的开始位置和结束位置;后面就是行数据(pg里面的行就是tuple)和special空间。整个page的结构比Oracle的数据块结构简单多了。
typedef struct PageHeaderData
{
PageXLogRecPtr pd_lsn;
uint16 pd_checksum;
uint16 pd_flags;
LocationIndex pd_lower;
LocationIndex pd_upper;
LocationIndex pd_special;
uint16 pd_pagesize_version;
TransactionId pd_prune_xid;
ItemIdData pd_linp[FLEXIBLE_ARRAY_MEMBER];
} PageHeaderData;
具体的长度和描述也都有详细说明:
Field | Type | Length | Description |
pd_lsn | PageXLogRecPtr | 8 bytes | LSN: next byte after last byte of WAL record for last change to this page |
pd_checksum | uint16 | 2 bytes | Page checksum |
pd_flags | uint16 | 2 bytes | Flag bits |
pd_lower | LocationIndex | 2 bytes | Offset to start of free space |
pd_upper | LocationIndex | 2 bytes | Offset to end of free space |
pd_special | LocationIndex | 2 bytes | Offset to start of special space |
pd_pagesize_version | uint16 | 2 bytes | Page size and layout version number information |
pd_prune_xid | TransactionId | 4 bytes | Oldest unpruned XMAX on page, or zero if none |
简单来说,pd_lsn是指最后修改过这个page的lsn(log sequence number),这个和wal(write ahead log,同oracle redo)中记录的lsn一致。数据落盘时redo必须先刷到wal,这个pd_lsn就记录了最后data落盘时的相关redo的lsn。
pd_checksum是校验和,在initdb初始化实例的时候通过-k参数指定开启,默认是关闭的,initdb之后不能修改,它基于FNV-1a hash算法,做了相应的更改。这个校验和与Oracle的checksum一样用于数据块在读入和写出内存时的校验。比如我们在内存中修改了一个数据块,写入到磁盘的时候,在内存里面先计算好checksum,数据块写完后再计算一遍cheksum是否和之前在内存中的一致,确保整个写出过程没有出错,保护数据结构不被破坏。
pd_flags有以下的值:
#define PD_HAS_FREE_LINES 0x0001
#define PD_PAGE_FULL 0x0002
#define PD_ALL_VISIBLE 0x0004
#define PD_VALID_FLAG_BITS 0x0007
pd_lower和pd_upper分别表示空闲空间起始位置和结束位置;pd_special在索引page才有效;pd_pagesize_version是page大小和page version的存储位,在不同数据库版本中,page version不一样:
数据库版本 | pd_pagesize_version | ||
<7.3 | 0 | ||
3 & 7.4 | 1 | ||
0 | 2 | ||
1 | 3 | ||
>8.3 | 4 |
prune_xid表示这个page上最早删除或者修改tuple的事务id,在vacuum操作的时候会用到。(pg没有undo,旧的数据也在page中,用vacuum来清理)
2.3 linp结构(行指针)
lp_off是tuple的开始的偏移量;lp_flags是标志位;lp_len记录了tuple的长度。
Field | Length | Description |
lp_off | 15 bits | offset to tuple |
lp_flags | 2 bits | State of iteam pointer |
lp_len | 15 bits | Byte length of tuple |
2.4 tuple header结构(行头)
typedef struct HeapTupleFields { TransactionId t_xmin; TransactionId t_xmax; union { CommandId t_cid; TransactionId t_xvac; } t_field3; } HeapTupleFields;
typedef struct DatumTupleFields { int32 datum_len_; int32 datum_typmod; Oid datum_typeid;
} DatumTupleFields;
struct HeapTupleHeaderData { union { HeapTupleFields t_heap; DatumTupleFields t_datum; } t_choice;
ItemPointerData t_ctid;
uint16 t_infomask2; uint16 t_infomask; uint8 t_hoff;
bits8 t_bits[FLEXIBLE_ARRAY_MEMBER];
}; (*这部分代码在class="lazy" data-src/include/access/htup_details.h) |
也有对应的长度和描述的相详细说明:
Field | Type | Length | Description |
t_xmin | TransactionId | 4 bytes | insert XID stamp |
t_xmax | TransactionId | 4 bytes | delete XID stamp |
t_cid | CommandId | 4 bytes | insert and/or delete CID stamp (overlays with t_xvac) |
t_xvac | TransactionId | 4 bytes | XID for VACUUM operation moving a row version |
t_ctid | ItemPointerData | 6 bytes | current TID of this or newer row version |
t_infomask2 | uint16 | 2 bytes | number of attributes, plus various flag bits |
t_infomask | uint16 | 2 bytes | various flag bits |
t_hoff | uint8 | 1 byte | offset to user data |
union是共享结构体,起作用的变量是最后一次赋值的成员。来看看tuple header的结构。
在HeapTupleFields中,t_xmin是插入这行tuple的事务id;t_xmax是删除或者锁住tuple的事务id;union结构中的t_cid是删除或者插入这个tuple的命令id,也就是命令序号;t_xvac是以前格式的vacuum full用到的事务id。
在DatumTupleFields中,datum_len_ 指tuple的长度;datum_typmod是记录的type;datum_typeid是记录的id。
页头HeapTupleHeaderData包含了union结构体中的两个变量HeapTupleFields和DatumTupleFields。t_ctid是tuple id,类似oracle的rowid,形式为(块号,行号)。
t_infomask2 表示属性和标志位
t_infomask 是flag标志位,具体值如下:
#define HEAP_HASNULL 0x0001 #define HEAP_HASVARWIDTH 0x0002 #define HEAP_HASEXTERNAL 0x0004 #define HEAP_HASOID 0x0008 #define HEAP_XMAX_KEYSHR_LOCK 0x0010 #define HEAP_COMBOCID 0x0020 #define HEAP_XMAX_EXCL_LOCK 0x0040 #define HEAP_XMAX_LOCK_ONLY 0x0080
#define HEAP_XMAX_SHR_LOCK (HEAP_XMAX_EXCL_LOCK | HEAP_XMAX_KEYSHR_LOCK)
#define HEAP_LOCK_MASK (HEAP_XMAX_SHR_LOCK | HEAP_XMAX_EXCL_LOCK | \ HEAP_XMAX_KEYSHR_LOCK) #define HEAP_XMIN_COMMITTED 0x0100 #define HEAP_XMIN_INVALID 0x0200 #define HEAP_XMIN_FROZEN (HEAP_XMIN_COMMITTED|HEAP_XMIN_INVALID) #define HEAP_XMAX_COMMITTED 0x0400 #define HEAP_XMAX_INVALID 0x0800 #define HEAP_XMAX_IS_MULTI 0x1000 #define HEAP_UPDATED 0x2000 #define HEAP_MOVED_OFF 0x4000 #define HEAP_MOVED_IN 0x8000 #define HEAP_MOVED (HEAP_MOVED_OFF | HEAP_MOVED_IN)
#define HEAP_XACT_MASK 0xFFF0 |
t_hoff表示tuple header的长度
t_bits记录了tuple中null值的列
三、实验
3.1 安装pageinspect
它在源码的crontrib目录下面
postgres@cs-> cd postgresql-10.4/contrib/pageinspect
make && make install postgres@cs-> make postgres@cs-> make install
create extension就好了 postgres@cs-> psql psql (10.4) Type "help" for help.
postgres=# CREATE EXTENSION pageinspect; CREATE EXTENSION
postgres=# \x Expanded display is on. postgres=# \dx List of installed extensions -[ RECORD 1 ]------------------------------------------------------ Name | pageinspect Version | 1.6 Schema | public Description | inspect the contents of database pages at a low level -[ RECORD 2 ]------------------------------------------------------ Name | plpgsql Version | 1.0 Schema | pg_catalog Description | PL/pgSQL procedural language |
3.2 创建建测试表t1,插入数据
这里可以看到1000行数据用了6个数据块来存储(这里数据块从0开始),第6个数据块包含了73条记录(tuple)
3.3 Pageinspect查看page
这里我们通过两个函数来查看
page_header 可以看到页头的数据
heap_pageitems 可以看到具体tuple的数据
3.3.1 page_header
postgres=# \xExpanded display is on.postgres=# select * from page_header(get_raw_page('t1',0));-[ RECORD 1 ]--------lsn | 0/1671188checksum | 0flags | 0lower | 772upper | 784special | 8192pagesize | 8192version | 4prune_xid | 0postgres=# |
可以看到第0个page的pd_lsn为0/1671188,checksum和flags都是0,这里没有开启checksum;tuple开始偏移是772(pd_lower),结束偏移是784(pd_upper),这个page是个表,所以它没有special,我们看到的sepcial就是8192了;pagesize是8192就是8K,version是4,没有需要清理的tuple,所以存储需要清理的tuple的最早事务的id就是0(prune_xid)。
3.3.2 heap_page_items
我们来看一行记录,可以看到它是第1行记录(lp=1),tuple的开始偏移量8160(lp_off),tuple的长度是32 bytes(lp_len为32,这个tuple是第一个插入的tuple,所以lp_off+lp_len=8160+32=8192),这行记录的插入事务id是557(t_min),和tuple的删除事务id是0(tmax),这里数据没有被删除,所以都是0。我们还可以看到t_ctid是(0,1),这里表示这个tuple是这个page中第一个块的第一条tuple;tinfomask2是2,t_infomask为2306,十六进制就是 0x0902 ,这个我们可以根据上面提到的值去看看具体的含义,0x0902 = 0x0100 + 0x0800 +0x0002;tuple头部结构(行头)的长度是24(t_hoff),t_data就是16进制存储的真正的数据了。
3.4 删除一行数据观察prune_xid
我们删除一行tuple可以看到prune_xid有了值,为559,这个559就是删除这个tuple的事务id(当前最早的删除或更改了tuple的事务id)
同样,我们可以看到lp为1的这个tuple的t_xmax为559,这里就是删除这行tuple的事务id。
PostgreSQL Page的物理结构相比Oracle的数据块来说简单很多了,源代码开放也便于学习和研究,pg是个很好很强大的数据库,值得好好学习。
以上是“怎么从零学习PostgreSQL Page结构”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注编程网行业资讯频道!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341