本文是基于postgresql 13的代码进行分析解读,演示是在centos8系统上进行。
+----------------+---------------------------------+
| PageHeaderData | linp1 linp2 linp3 ... |
+-----------+----+---------------------------------+
| ... linpN | |
+-----------+--------------------------------------+
| ^ pd_lower |
| |
| v pd_upper |
+-------------+------------------------------------+
| | tupleN ... |
+-------------+------------------+-----------------+
| ... tuple3 tuple2 tuple1 | "special space" |
+--------------------------------+-----------------+
^ pd_special
(1) PageHeaderData
(2) 行指针,也就是图中的linp1;占4字节,由offset ,flag,length, flag占2bytes;
(3)空闲空间;
(4)tuple数据
(5)special空间
2、PageHeaderData介绍
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;
typedef PageHeaderData *PageHeader;
Pd_lsn : 最后修改对应WAL的 (Log Sequence Number ),这个lsn也被称为PageLSN,它确定和记录了最后更改此页的xlog记录的LSN,把数据页和WAL日志关联,用于恢复数据时校验日志文件和数据文件的一致性
Pd_checksum:
Pd_flags:
取值有:
#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_lower指向空闲空间的起始位置,pd_upper指向空闲空间的结束位置;linp空间从pd_lower指向位置开始分配,并向后移动; tuple数据从pd_upper位置往前分配,分配后pd_upper向前移动。
Pd_special: special空间的起始位置,结束位置就是块大小
Pd_pagesize_version: 为了兼容以前版本,把pagesize和version放在了2字节中。低8bit是版本,高8bit是pagesize,规定pagesize是256的倍数;8bit表示的最大的pagesize是32KB;
pageSize获取方法:
#define PageGetPageSize(page) \
((Size) (((PageHeader) (page))->pd_pagesize_version & (uint16) 0xFF00))
version的取值如下:
/*
* Page layout version number 0 is for pre-7.3 Postgres releases.
* Releases 7.3 and 7.4 use 1, denoting a new HeapTupleHeader layout.
* Release 8.0 uses 2; it changed the HeapTupleHeader layout again.
* Release 8.1 uses 3; it redefined HeapTupleHeader infomask bits.
* Release 8.3 uses 4; it changed the HeapTupleHeader layout again, and
* added the pd_flags field (by stealing some bits from pd_tli),
* as well as adding the pd_prune_xid field (which enlarges the header).
*
* As of Release 9.3, the checksum version must also be considered when
* handling pages.
*/
#define PG_PAGE_LAYOUT_VERSION 4
#define PG_DATA_CHECKSUM_VERSION 1
version获取方法:
#define PageGetPageLayoutVersion(page) \
(((PageHeader) (page))->pd_pagesize_version & 0x00FF)
Pd_prune_xid:上一次块内整理时的xid;在索引page中不使用。
Pd_linp:行指针数据,行指针的结构有三个成员组成(off,flag,len),占4字节空间;
Page空间的有效性,即指针不为空
#define PageIsValid(page) PointerIsValid(page)
Pageheader size计算:
#define SizeOfPageHeaderData (offsetof(PageHeaderData, pd_linp))
获取page内容,跳过pageheader
#define PageGetContents(page) \
((char *) (page) + MAXALIGN(SizeOfPageHeaderData))
page是否是空的,即page中一条tuple都没有,pd_lower指向pageheader结尾
#define PageIsEmpty(page) \
(((PageHeader) (page))->pd_lower <= SizeOfPageHeaderData)
page是否是新的,还没有被pageInit初始化
#define PageIsNew(page) (((PageHeader) (page))->pd_upper == 0)
设置pagesize和version
#define PageSetPageSizeAndVersion(page, size, version) \
( \
AssertMacro(((size) & 0xFF00) == (size)), \
AssertMacro(((version) & 0x00FF) == (version)), \
((PageHeader) (page))->pd_pagesize_version = (size) | (version) \
)
3、行指针介绍
typedef struct ItemIdData
{
unsigned lp_off:15, /* offset to tuple (from start of page) */
lp_flags:2, /* state of line pointer, see below */
lp_len:15; /* byte length of tuple */
} ItemIdData;
typedef ItemIdData *ItemId;
这里为了节省空间,按bit进行了划分,lp_off/lp_len的上限也是pagesize的上限;
获取第offsetNumber行指针
#define PageGetItemId(page, offsetNumber) \
((ItemId) (&((PageHeader) (page))->pd_linp[(offsetNumber) - 1]))
获得最大行指针数量
#define PageGetMaxOffsetNumber(page) \
(((PageHeader) (page))->pd_lower <= SizeOfPageHeaderData ? 0 : \
((((PageHeader) (page))->pd_lower - SizeOfPageHeaderData) \
/ sizeof(ItemIdData)))
4、空闲空间介绍
Size
PageGetFreeSpace(Page page)
{
int space;
/*
* Use signed arithmetic here so that we behave sensibly if pd_lower >
* pd_upper.
*/
space = (int) ((PageHeader) page)->pd_upper -
(int) ((PageHeader) page)->pd_lower;
if (space < (int) sizeof(ItemIdData))
return 0;
space -= sizeof(ItemIdData);
return (Size) space;
}
是否有空闲行
#define PageHasFreeLinePointers(page) \
(((PageHeader) (page))->pd_flags & PD_HAS_FREE_LINES)
page是否满
#define PageIsFull(page) \
(((PageHeader) (page))->pd_flags & PD_PAGE_FULL)
tuple是否都可见
#define PageIsAllVisible(page) \
(((PageHeader) (page))->pd_flags & PD_ALL_VISIBLE)
5、tuple数据介绍
获取itemid指定的tuple 指针
#define PageGetItem(page, itemId) \
( \
AssertMacro(PageIsValid(page)), \
AssertMacro(ItemIdHasStorage(itemId)), \
(Item)(((char *)(page)) + ItemIdGetOffset(itemId)) \
)
6、special空间介绍
获取special空间大小
#define PageGetSpecialSize(page) \
((uint16) (PageGetPageSize(page) - ((PageHeader)(page))->pd_special))
获取special位置
#define PageGetSpecialPointer(page) \
( \
AssertMacro(PageValidateSpecialPointer(page)), \
(char *) ((char *) (page) + ((PageHeader) (page))->pd_special) \
)
作者邮箱:[email protected]
如有错误或者疏漏欢迎指出,互相学习。
注:未经同意,不得转载!