第 13 课 PostgreSQL 存储之Page(页面)源码分析
时间:2019-05-23 13:23 来源:linux.it.net.cn 作者:IT
在文章:PostgreSQL 数据存储结构 中我们介绍了控制页和数据页的基本存储结构,那是从物理上进行说明各种页面的用途。
下面我们是从代码逻辑上来分析页面是如何进行操作和控制的。
页面布局示意图
PageHeader
先简单的看一下源代码中定义的Page头部信息结构体,中文是我自己的理解:
源码位置:/src/include/storage/bufpage.h
typedef struct PageHeaderData
{
/* XXX LSN is member of *any* block, not only page-organized ones */
/*
日志文件信息,保存了该页面最后一次被修改的操作对应到确切的日志文件
位置,包括了日志文件ID和日志文件偏移量
*/
XLogRecPtr pd_lsn; /* LSN: next byte after last byte of xlog
* record for last change to this page */
uint16 pd_tli; /* least significant bits of the TimeLineID
* containing the LSN */
/* 用来表示页面状态 */
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 */
/* 特殊用途空间的起始位置,结束位置是page尾部,直到页面结束 */
LocationIndex pd_special; /* offset to start of special space */
uint16 pd_pagesize_version;
/* 页面类型: 有多种控制页面类型和数据页面 */
uint8 pd_type;
/* 物理存储与逻辑存储的关联对象的OID */
Oid pd_oid; /* oid of related object,
* pg_class.oid for IAM, pg_class.relfilenode for others */
/* 数据开始位置 */
ItemIdData pd_linp[1]; /* beginning of line pointer array */
} PageHeaderData;
header用来保存该page相关的数据。
-
pd_lsn、pd_tli 记录日志相关的信息。
-
pd_flags 表示页面状态
PD_ALL_VISIBLE(0x0001) - 表示所有元组可以访问。
PD_VALID_FLAG_BITS(0x0003) - 加密存储相关。
PG_PAGE_ENCRYPTED(0x0002) - 支持统一存储加密引擎。
-
pd_lower
空闲空间起始位置,随着插入和删除操作位置发生变化。初始化时就是pd_linp的偏移位置。
-
pd_upper
空闲空间结束位置,随着插入和删除操作位置发生变化。初始化时与pd_special相同。
-
pd_special
相当于画了一条线,从pd_special这个位置到page的结尾,都是special的地盘,普通插入Tuple,都不许进入这个私有地盘。而且这个pd_special一旦初始化之后,这个值就不会动了。
主要用于加密存储是保存加密相关的数据。
-
pd_pagesize_version
高位字节表示page大小。
低位字节表示版本号(PG_PAGE_LAYOUT_VERSION),代码写死。
宏PageSetPageSizeAndVersion用于设置大小和版本。
宏PageGetPageSize()获取Page大小。
宏PageGetPageLayoutVersion()获取版本号。
对应的实现:
#define PageSetPageSizeAndVersion(page, size, version) \
( \
AssertMacro(((size) & 0xFF00) == (size)), \
AssertMacro(((version) & 0x00FF) == (version)), \
((PageHeader) (page))->pd_pagesize_version = (size) | (version) \
)
#define PageGetPageSize(page) \
((Size) (((PageHeader) (page))->pd_pagesize_version & (uint16) 0xFF00))
#define PageGetPageLayoutVersion(page) \
(((PageHeader) (page))->pd_pagesize_version & 0x00FF)
-
pd_type
有这些类型:P_GAM、P_IAM、P_PFS、P_DCM、P_BCM、P_SGAM、P_VM。
对应的定义:
typedef enum PageType{
P_TEMPDATA,
P_HEAP,
P_BTREE,
P_HASH,
P_GIN,
P_GIST,
P_SEQUENCE,
P_GAM,
P_PFS,
P_SGAM,
P_BCM,
P_DCM,
P_IAM,
P_VM, /* vm page*/
P_UNKNOWN,
}PageType;
Page初始化
通过如下函数初始化页面:
void
PageInit(Page page, Size pageSize, Size specialSize, PageType type)
{
PageHeader p = (PageHeader) page;
uint16 pd_flags = p->pd_flags;
specialSize = MAXALIGN_DISK(specialSize);
Assert(pageSize == BLCKSZ);
Assert(pageSize > specialSize + SizeOfPageHeaderData);
/* Make sure all fields of page are zero, as well as unused space */
MemSet(p, 0, pageSize);
/* p->pd_flags = 0; done by above MemSet */
PageSetPageSizeAndVersion(page, pageSize, PG_PAGE_LAYOUT_VERSION);
PageSetPageType(page, type);
PageSetOid(page, InvalidOid); /* we will set it later for heap page */
/* Support the uniform store encryption engine. */
p->pd_lower = SizeOfPageHeaderData;
if ((pd_flags & PG_PAGE_ENCRYPTED) && !PageIsCtrlPage(page))
p->pd_special = pageSize - specialSize -
G_EncryptData->block_key_num * (G_EncryptData->key_len + G_EncryptData->verify_len);
else
p->pd_special = pageSize - specialSize;
p->pd_upper = p->pd_special;
/*
* Empty page is all visible. We must set PD_ALL_VISIBLE because
* recycled page's "visibility map bit" may be set before. (Recycled page --
* means page was deallocated by Vacuum or something else, and then
* be allocated.)
*/
PageSetAllVisible(p);
if ((pd_flags & PG_PAGE_ENCRYPTED) && !PageIsCtrlPage(page))
PageSetEncrypted(p);
}
待分析...
Page有效性检查
通过如下函数实现:
bool
PageHeaderIsValid(PageHeader page)
{
char *pagebytes;
int i;
/* Check normal case */
/* Support the uniform store encryption engine. */
if (PageGetPageSize(page) == BLCKSZ &&
PageGetPageLayoutVersion(page) == PG_PAGE_LAYOUT_VERSION &&
(page->pd_flags & ~PD_VALID_FLAG_BITS) == 0 &&
page->pd_lower >= SizeOfPageHeaderData &&
page->pd_lower <= page->pd_upper &&
page->pd_upper <= page->pd_special &&
(PageIsEncrypted(page) ?
page->pd_special <= BLCKSZ - G_EncryptData->block_key_num * (G_EncryptData->key_len + G_EncryptData->verify_len) :
page->pd_special <= BLCKSZ) &&
page->pd_special == MAXALIGN_DISK(page->pd_special))
return true;
/* Check all-zeroes case */
pagebytes = (char *) page;
for (i = 0; i < BLCKSZ; i++)
{
/* Support the uniform store encryption engine. */
if (pagebytes[i] != 0 && pagebytes[i] != 2)
return false;
}
return true;
}
待分析...
Insert操作分析
当初始化的时候,pd_lower设置为SizeOfPageHeaderData,pd_upper设置为和pd_special一样。但是注意,这个lower和upper不是固定的,随着Tuple的不断插入,lower变大,而upper不断变小。当我们每插入一条Tuple,需要在当前的lower位置再分配一个Item,记录Tuple的长度,Tuple的起始位置offset,还有flag信息。这个Page Header中的pd_lower就是记录分配下一个Item的起始位置。所以如果不断插入,lower不断增加,每增加一条Tuple,就要分配一个Item(4个字节)
对应实现函数:
-
PageAddItem
OffsetNumber
PageAddItem(Page page,
Item item,
Size size,
OffsetNumber offsetNumber,
Size tupleSize,
ItemIdFlags flags)
待分析...
Delete操作分析
对应实现函数:
-
PageIndexMultiDelete
void
PageIndexMultiDelete(Page page, OffsetNumber *itemnos, int nitems)
offnum指示第几个记录,offnum是从1开始计数的,查找对应item 是offnum-1.
我们找到Item,就可以找到Tuple对应的offset和size。
待分析...
(责任编辑:IT)
在文章:PostgreSQL 数据存储结构 中我们介绍了控制页和数据页的基本存储结构,那是从物理上进行说明各种页面的用途。 下面我们是从代码逻辑上来分析页面是如何进行操作和控制的。 页面布局示意图PageHeader
先简单的看一下源代码中定义的Page头部信息结构体,中文是我自己的理解: typedef struct PageHeaderData { /* XXX LSN is member of *any* block, not only page-organized ones */ /* 日志文件信息,保存了该页面最后一次被修改的操作对应到确切的日志文件 位置,包括了日志文件ID和日志文件偏移量 */ XLogRecPtr pd_lsn; /* LSN: next byte after last byte of xlog * record for last change to this page */ uint16 pd_tli; /* least significant bits of the TimeLineID * containing the LSN */ /* 用来表示页面状态 */ 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 */ /* 特殊用途空间的起始位置,结束位置是page尾部,直到页面结束 */ LocationIndex pd_special; /* offset to start of special space */ uint16 pd_pagesize_version; /* 页面类型: 有多种控制页面类型和数据页面 */ uint8 pd_type; /* 物理存储与逻辑存储的关联对象的OID */ Oid pd_oid; /* oid of related object, * pg_class.oid for IAM, pg_class.relfilenode for others */ /* 数据开始位置 */ ItemIdData pd_linp[1]; /* beginning of line pointer array */ } PageHeaderData; header用来保存该page相关的数据。
#define PageSetPageSizeAndVersion(page, size, version) \ ( \ AssertMacro(((size) & 0xFF00) == (size)), \ AssertMacro(((version) & 0x00FF) == (version)), \ ((PageHeader) (page))->pd_pagesize_version = (size) | (version) \ ) #define PageGetPageSize(page) \ ((Size) (((PageHeader) (page))->pd_pagesize_version & (uint16) 0xFF00)) #define PageGetPageLayoutVersion(page) \ (((PageHeader) (page))->pd_pagesize_version & 0x00FF)
typedef enum PageType{ P_TEMPDATA, P_HEAP, P_BTREE, P_HASH, P_GIN, P_GIST, P_SEQUENCE, P_GAM, P_PFS, P_SGAM, P_BCM, P_DCM, P_IAM, P_VM, /* vm page*/ P_UNKNOWN, }PageType; Page初始化通过如下函数初始化页面: void PageInit(Page page, Size pageSize, Size specialSize, PageType type) { PageHeader p = (PageHeader) page; uint16 pd_flags = p->pd_flags; specialSize = MAXALIGN_DISK(specialSize); Assert(pageSize == BLCKSZ); Assert(pageSize > specialSize + SizeOfPageHeaderData); /* Make sure all fields of page are zero, as well as unused space */ MemSet(p, 0, pageSize); /* p->pd_flags = 0; done by above MemSet */ PageSetPageSizeAndVersion(page, pageSize, PG_PAGE_LAYOUT_VERSION); PageSetPageType(page, type); PageSetOid(page, InvalidOid); /* we will set it later for heap page */ /* Support the uniform store encryption engine. */ p->pd_lower = SizeOfPageHeaderData; if ((pd_flags & PG_PAGE_ENCRYPTED) && !PageIsCtrlPage(page)) p->pd_special = pageSize - specialSize - G_EncryptData->block_key_num * (G_EncryptData->key_len + G_EncryptData->verify_len); else p->pd_special = pageSize - specialSize; p->pd_upper = p->pd_special; /* * Empty page is all visible. We must set PD_ALL_VISIBLE because * recycled page's "visibility map bit" may be set before. (Recycled page -- * means page was deallocated by Vacuum or something else, and then * be allocated.) */ PageSetAllVisible(p); if ((pd_flags & PG_PAGE_ENCRYPTED) && !PageIsCtrlPage(page)) PageSetEncrypted(p); } 待分析... Page有效性检查通过如下函数实现: bool PageHeaderIsValid(PageHeader page) { char *pagebytes; int i; /* Check normal case */ /* Support the uniform store encryption engine. */ if (PageGetPageSize(page) == BLCKSZ && PageGetPageLayoutVersion(page) == PG_PAGE_LAYOUT_VERSION && (page->pd_flags & ~PD_VALID_FLAG_BITS) == 0 && page->pd_lower >= SizeOfPageHeaderData && page->pd_lower <= page->pd_upper && page->pd_upper <= page->pd_special && (PageIsEncrypted(page) ? page->pd_special <= BLCKSZ - G_EncryptData->block_key_num * (G_EncryptData->key_len + G_EncryptData->verify_len) : page->pd_special <= BLCKSZ) && page->pd_special == MAXALIGN_DISK(page->pd_special)) return true; /* Check all-zeroes case */ pagebytes = (char *) page; for (i = 0; i < BLCKSZ; i++) { /* Support the uniform store encryption engine. */ if (pagebytes[i] != 0 && pagebytes[i] != 2) return false; } return true; } 待分析... Insert操作分析当初始化的时候,pd_lower设置为SizeOfPageHeaderData,pd_upper设置为和pd_special一样。但是注意,这个lower和upper不是固定的,随着Tuple的不断插入,lower变大,而upper不断变小。当我们每插入一条Tuple,需要在当前的lower位置再分配一个Item,记录Tuple的长度,Tuple的起始位置offset,还有flag信息。这个Page Header中的pd_lower就是记录分配下一个Item的起始位置。所以如果不断插入,lower不断增加,每增加一条Tuple,就要分配一个Item(4个字节) 对应实现函数:
OffsetNumber PageAddItem(Page page, Item item, Size size, OffsetNumber offsetNumber, Size tupleSize, ItemIdFlags flags) 待分析... Delete操作分析对应实现函数:
void PageIndexMultiDelete(Page page, OffsetNumber *itemnos, int nitems)
offnum指示第几个记录,offnum是从1开始计数的,查找对应item 是offnum-1. (责任编辑:IT) |