本節概述了 PostgreSQL 資料表和索引中使用的頁面格式。[17] 序列和TOAST資料表的格式與一般資料表相同。
在以下說明中,位元組 假設包含 8 個位元。此外,術語 項目 是指儲存在頁面上的個別資料值。在資料表中,項目是一列;在索引中,項目是一個索引條目。
每個資料表和索引都儲存為固定大小(通常為 8 kB,雖然可以在編譯伺服器時選擇不同的頁面大小)的 頁面 陣列。在資料表中,所有頁面在邏輯上都是等效的,因此特定項目(列)可以儲存在任何頁面中。在索引中,第一頁通常保留為保存控制資訊的 元頁面,並且索引中可能存在不同類型的頁面,具體取決於索引存取方法。
表 65.2 顯示了頁面的整體佈局。每個頁面都有五個部分。
表 65.2. 整體頁面佈局
項目 | 描述 |
---|---|
PageHeaderData | 長 24 個位元組。包含有關頁面的一般資訊,包括可用空間指標。 |
ItemIdData | 指向實際項目的項目識別碼陣列。每個條目都是一個(偏移量,長度)對。每個項目 4 個位元組。 |
可用空間 | 未分配的空間。新的項目識別碼從該區域的開頭分配,新的項目從結尾分配。 |
項目 | 實際項目本身。 |
特殊空間 | 索引存取方法特定的資料。不同的方法儲存不同的資料。在普通資料表中為空。 |
每個頁面的前 24 個位元組由頁面標頭 (PageHeaderData
) 組成。其格式詳述於 表 65.3。第一個欄位追蹤與此頁面相關的最新 WAL 條目。如果啟用 資料校驗和,則第二個欄位包含頁面校驗和。接下來是一個包含標誌位的 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。
表 65.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 樹索引儲存到頁面左側和右側兄弟頁面的連結,以及一些與索引結構相關的其他資料。普通表根本不使用特殊區段(透過將 pd_special
設置為等於頁面大小來表示)。
圖 65.1 說明了這些部分如何在頁面中佈局。
圖 65.1. 頁面佈局
所有表格列都以相同的方式組織。有一個固定大小的標頭(在大多數機器上佔用 23 個位元組),後跟一個可選的空值位元圖、一個可選的物件 ID 欄位和使用者資料。標頭在表格 65.4 中有詳細說明。實際的使用者資料(列的欄位)從 t_hoff
指示的偏移量開始,該偏移量必須始終是平台 MAXALIGN 距離的倍數。只有在 t_infomask
中設定了 HEAP_HASNULL 位元時,才存在空值位元圖。如果存在,它將緊跟在固定標頭之後開始,並佔用足夠的位元組,以便每個資料欄位有一個位元(也就是說,位元的數量等於 t_infomask2
中的屬性計數)。在此位元清單中,1 位元表示非空值,0 位元表示空值。當位元圖不存在時,假定所有欄位都是非空值。只有在 t_infomask
中設定了 HEAP_HASOID_OLD 位元時,才存在物件 ID。如果存在,它將出現在 t_hoff
邊界之前。使 t_hoff
成為 MAXALIGN 倍數所需的任何填充將出現在空值位元圖和物件 ID 之間。(這反過來確保物件 ID 適當地對齊。)
表格 65.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
。除了只有固定寬度欄位且沒有空值的情況外,沒有辦法直接取得特定屬性。所有這些技巧都包含在函數 heap_getattr、fastgetattr 和 heap_getsysattr 中。
要讀取資料,您需要依次檢查每個屬性。首先,根據空值位元圖檢查欄位是否為 NULL。如果是,則轉到下一個。然後確保您具有正確的對齊方式。如果該欄位是固定寬度的欄位,那麼所有位元組都只需放置即可。如果它是可變長度的欄位 (attlen = -1),那麼它會更複雜一些。所有可變長度資料類型都共享通用標頭結構 struct varlena
,其中包含儲存值的總長度和一些旗標位元。根據旗標,資料可以是內嵌的或在TOAST表格中;它也可能被壓縮(請參閱章節 65.2)。
如果您在文件中看到任何不正確、與特定功能的使用體驗不符或需要進一步澄清的地方,請使用此表格來回報文件問題。