怎么从零学习PostgreSQL Page结构

76次阅读
没有评论

共计 8396 个字符,预计需要花费 21 分钟才能阅读完成。

这篇文章主要为大家展示了“怎么从零学习 PostgreSQL Page 结构”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让丸趣 TV 小编带领大家一起研究并学习一下“怎么从零学习 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

{

 /* XXX LSN is member of *any* block, not only page-organized ones */

 PageXLogRecPtr pd_lsn; /* LSN: next byte after last byte of xlog

 * record for last change to this page */

 uint16 pd_checksum; /* checksum */

 uint16 pd_flags; /* flag bits, see below */

 LocationIndex pd_lower; /* offset to start of free space */

 LocationIndex pd_upper; /* offset to end of free space */

 LocationIndex pd_special; /* offset to start of special space */

 uint16 pd_pagesize_version;

 TransactionId pd_prune_xid; /* oldest prunable XID, or zero if none */

 ItemIdData pd_linp[FLEXIBLE_ARRAY_MEMBER]; /* line pointer array */

} 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 有以下的值:

/*

 * pd_flags contains the following flag bits.  Undefined bits are initialized

 * to zero and may be used in the future.

 *

 * PD_HAS_FREE_LINES is set if there are any LP_UNUSED line pointers before

 * pd_lower.  This should be considered a hint rather than the truth, since

 * changes to it are not WAL-logged.

 *

 * PD_PAGE_FULL is set if an UPDATE doesn t find enough free space in the

 * page for its new tuple version; this suggests that a prune is needed.

 * Again, this is just a hint.

 */

#define PD_HAS_FREE_LINES 0x0001 /* are there any unused line pointers? */

#define PD_PAGE_FULL 0x0002 /* not enough free space for new tuple? */

#define PD_ALL_VISIBLE 0x0004 /* all tuples on page are visible to

 * everyone */

 

#define PD_VALID_FLAG_BITS 0x0007 /* OR of all valid pd_flags bits */

 

pd_lower 和 pd_upper 分别表示空闲空间起始位置和结束位置;pd_special 在索引 page 才有效;pd_pagesize_version 是 page 大小和 page version 的存储位,在不同数据库版本中,page version 不一样:

数据库版本

pd_pagesize_version

7.3

0

7.3 7.4

1

8.0

2

8.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; /* inserting xact ID */

 TransactionId t_xmax; /* deleting or locking xact ID */

 union

 {

 CommandId t_cid; /* inserting or deleting command ID, or both */

 TransactionId t_xvac; /* old-style VACUUM FULL xact ID */

 } t_field3;

} HeapTupleFields;

 

typedef struct DatumTupleFields

{

 int32 datum_len_; /* varlena header (do not touch directly!) */

 int32 datum_typmod; /* -1, or identifier of a record type */

 Oid datum_typeid; /* composite type OID, or RECORDOID */

 

 /*

 * Note: field ordering is chosen with thought that Oid might someday

 * widen to 64 bits.

 */

} DatumTupleFields;

 

struct HeapTupleHeaderData

{

 union

 {

 HeapTupleFields t_heap;

 DatumTupleFields t_datum;

 } t_choice;

 

 ItemPointerData t_ctid; /* current TID of this or newer tuple (or a

 * speculative insertion token) */

 /* Fields below here must match MinimalTupleData! */

 uint16 t_infomask2; /* number of attributes + various flags */

 uint16 t_infomask; /* various flag bits, see below */

 uint8 t_hoff; /* sizeof header incl. bitmap, padding */

 /* ^ – 23 bytes – ^ */

 bits8 t_bits[FLEXIBLE_ARRAY_MEMBER]; /* bitmap of NULLs */

 /* MORE DATA FOLLOWS AT END OF STRUCT */

};

(* 这部分代码在 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 标志位,具体值如下:

/*

 * information stored in t_infomask:

 */

#define HEAP_HASNULL 0x0001 /* has null attribute(s) */

#define HEAP_HASVARWIDTH 0x0002 /* has variable-width attribute(s) */

#define HEAP_HASEXTERNAL 0x0004 /* has external stored attribute(s) */

#define HEAP_HASOID 0x0008 /* has an object-id field */

#define HEAP_XMAX_KEYSHR_LOCK 0x0010 /* xmax is a key-shared locker */

#define HEAP_COMBOCID 0x0020 /* t_cid is a combo cid */

#define HEAP_XMAX_EXCL_LOCK 0x0040 /* xmax is exclusive locker */

#define HEAP_XMAX_LOCK_ONLY 0x0080 /* xmax, if valid, is only a locker */

 

 /* xmax is a shared locker */

#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 /* t_xmin committed */

#define HEAP_XMIN_INVALID 0x0200 /* t_xmin invalid/aborted */

#define HEAP_XMIN_FROZEN (HEAP_XMIN_COMMITTED|HEAP_XMIN_INVALID)

#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 */

#define HEAP_UPDATED 0x2000 /* this is UPDATEd version of row */

#define HEAP_MOVED_OFF 0x4000 /* moved to another place by pre-9.0

 * VACUUM FULL; kept for binary

 * upgrade support */

#define HEAP_MOVED_IN 0x8000 /* moved from another place by pre-9.0

 * VACUUM FULL; kept for binary

 * upgrade support */

#define HEAP_MOVED (HEAP_MOVED_OFF | HEAP_MOVED_IN)

 

#define HEAP_XACT_MASK 0xFFF0 /* visibility-related bits */

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 结构”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注丸趣 TV 行业资讯频道!

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-07-26发表,共计8396字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)