本節概述了 PostgreSQL 表和索引內部使用的頁面格式。[19] 序列和TOAST表與普通表格式相同。
在以下解釋中,假定一個 位元組 包含 8 位。此外,術語 項 指儲存在頁面上的單個數據值。在表中,一項是一行;在索引中,一項是一個索引條目。
每個表和索引都儲存為固定大小(通常為 8 kB,儘管在編譯伺服器時可以選擇不同的頁面大小)的 頁面 陣列。在表中,所有頁面在邏輯上都是等效的,因此特定的項(行)可以儲存在任何頁面上。在索引中,第一個頁面通常保留為 元資料頁 ,用於儲存控制資訊,並且根據索引訪問方法,索引中可以存在不同型別的頁面。
表 66.2 顯示了頁面的整體佈局。每個頁面有五個部分。
表 66.2. 整體頁面佈局
項 | 描述 |
---|---|
PageHeaderData | 24 位元組長。包含關於頁面的常規資訊,包括空閒空間指標。 |
ItemIdData | 指向實際項的項識別符號陣列。每個條目是一個(偏移量,長度)對。每項 4 位元組。 |
Free space | 未分配的空間。新的項識別符號從該區域的開頭分配,新的項從該區域的末尾分配。 |
Items | 實際的項本身。 |
Special space | 特定於索引訪問方法的資料。不同的方法儲存不同的資料。普通表中為空。 |
每個頁面的前 24 位元組是一個頁面頭(PageHeaderData
)。其格式在 表 66.3 中有詳細說明。第一個欄位跟蹤與此頁面相關的最新 WAL 條目。第二個欄位包含頁面校驗和(如果啟用了 -k
)。接下來是一個 2 位元組的欄位,包含標誌位。之後是三個 2 位元組的整數字段(pd_lower
、pd_upper
和 pd_special
)。它們包含從頁面開始到未分配空間開始、到未分配空間結束以及到特殊空間開始的位元組偏移量。頁面頭的接下來的 2 位元組 pd_pagesize_version
儲存頁面大小和版本指示符。從 PostgreSQL 8.3 開始,版本號為 4;PostgreSQL 8.1 和 8.2 使用版本號 3;PostgreSQL 8.0 使用版本號 2;PostgreSQL 7.3 和 7.4 使用版本號 1;之前的版本使用版本號 0。(基本頁面佈局和頭部格式在這些版本中大部分沒有改變,但堆行頭部的佈局發生了變化。)頁面大小基本上僅作為交叉檢查存在;一個安裝中不支援多種頁面大小。最後一個欄位是一個提示,表明是否可能有效地修剪頁面:它跟蹤頁面上最舊的未修剪 XMAX。
表 66.3. PageHeaderData 佈局
欄位 | 型別 | 長度 | 描述 |
---|---|---|---|
pd_lsn | PageXLogRecPtr | 8 位元組 | LSN:此頁面最後一次更改的 WAL 記錄的最後一個位元組之後的下一個位元組 |
pd_checksum | uint16 | 2 位元組 | 頁面校驗和 |
pd_flags | uint16 | 2 位元組 | 標誌位 |
pd_lower | LocationIndex | 2 位元組 | 指向空閒空間開始的偏移量 |
pd_upper | LocationIndex | 2 位元組 | 指向空閒空間結束的偏移量 |
pd_special | LocationIndex | 2 位元組 | 指向特殊空間開始的偏移量 |
pd_pagesize_version | uint16 | 2 位元組 | 頁面大小和佈局版本號資訊 |
pd_prune_xid | TransactionId | 4 位元組 | 頁面上最舊的未修剪 XMAX,如果不存在則為零 |
所有詳細資訊均可在 src/include/storage/bufpage.h
中找到。
頁面頭之後是項識別符號(ItemIdData
),每個項需要四個位元組。項識別符號包含項開始處的位元組偏移量、其位元組長度以及一些影響其解釋的屬性位。新項識別符號根據需要從未分配空間的開頭分配。項識別符號的數量可以透過檢視 pd_lower
來確定,該欄位在分配新識別符號時會增加。由於項識別符號在被釋放之前永遠不會被移動,因此其索引可以長期用於引用項,即使項本身在頁面上被移動以壓縮空閒空間。事實上,PostgreSQL 建立的每個指向項的指標(ItemPointer
,也稱為 CTID
)都由頁面編號和項識別符號的索引組成。
項本身儲存在從未分配空間末尾向後分配的空間中。具體結構取決於表要包含的內容。表和序列都使用一個名為 HeapTupleHeaderData
的結構,下面將進行描述。
最後一個部分是 “特殊部分” ,它可以包含訪問方法希望儲存的任何內容。例如,b-tree 索引儲存指向頁面左右兄弟節點的連結,以及與索引結構相關的其他一些資料。普通表根本不使用特殊部分(透過將 pd_special
設定為等於頁面大小來指示)。
圖 66.1 說明了這些部分在頁面中的佈局方式。
圖 66.1. 頁面佈局
所有錶行都以相同的方式構建。有一個固定大小的頭部(在大多數機器上佔用 23 位元組),後面是可選的 NULL 點陣圖、可選的物件 ID 欄位和使用者資料。頭部在 表 66.4 中有詳細說明。實際的使用者資料(行的列)從 t_hoff
指定的偏移量開始,該偏移量必須始終是平臺 MAXALIGN 距離的倍數。只有在 t_infomask
中設定了 HEAP_HASNULL 位時,才存在 NULL 點陣圖。如果存在,它緊跟在固定頭部之後,並佔用足夠的位元組數以使每列資料(即,等於 t_infomask2
中屬性計數位數的位數)有一個位。在這個位列表中,1 表示非 NULL,0 表示 NULL。當不存在點陣圖時,假定所有列都為非 NULL。只有在 t_infomask
中設定了 HEAP_HASOID_OLD 位時,才存在物件 ID。如果存在,它出現在 t_hoff
邊界之前。為了使 t_hoff
成為 MAXALIGN 的倍數而需要的任何填充將出現在 NULL 點陣圖和物件 ID 之間。(這反過來確保了物件 ID 的對齊方式正確。)
表 66.4. HeapTupleHeaderData 佈局
欄位 | 型別 | 長度 | 描述 |
---|---|---|---|
t_xmin | TransactionId | 4 位元組 | 插入 XID 時間戳 |
t_xmax | TransactionId | 4 位元組 | 刪除 XID 時間戳 |
t_cid | CommandId | 4 位元組 | 插入和/或刪除 CID 時間戳(與 t_xvac 重疊) |
t_xvac | TransactionId | 4 位元組 | 移動行版本的 VACUUM 操作的 XID |
t_ctid | ItemPointerData | 6 位元組 | 當前行版本或更新的行版本的 TID |
t_infomask2 | uint16 | 2 位元組 | 屬性數量,以及各種標誌位 |
t_infomask | uint16 | 2 位元組 | 各種標誌位 |
t_hoff | uint8 | 1 位元組 | 指向使用者資料的偏移量 |
所有詳細資訊均可在 src/include/access/htup_details.h
中找到。
解釋實際資料只能透過從其他表(主要是 pg_attribute
)獲取的資訊來完成。識別字段位置所需的關鍵值是 attlen
和 attalign
。除了只有固定寬度欄位且沒有 NULL 值的情況外,沒有辦法直接獲取特定屬性。所有這些技巧都封裝在函式 heap_getattr、fastgetattr 和 heap_getsysattr 中。
要讀取資料,您需要依次檢查每個屬性。首先根據 NULL 點陣圖檢查欄位是否為 NULL。如果為 NULL,則繼續下一個。然後確保您具有正確的對齊方式。如果欄位是固定寬度的,則所有位元組都按順序放置。如果它是可變長度欄位(attlen = -1),則會更復雜。所有可變長度資料型別都共享通用的結構 struct varlena
,該結構包括儲存值的總長度和一些標誌位。根據標誌位,資料可以是內聯的,也可以在TOAST表中;它也可能被壓縮(參見 第 66.2 節)。
如果您在文件中發現任何不正確、與您對特定功能的體驗不符或需要進一步澄清的內容,請使用 此表單 報告文件問題。