Chapter 51. 分頁文件

一份描述數據庫文件缺省頁面格式的文檔。

本章提供一個 PostgreSQL 的表和索引所使用的頁面格式的概述。 (索引訪問模式不需要使用這些頁面格式。目前,所有索引方法的確都使用這個基本格式, 但保留在索引元數據頁裡的數據通常並不準確地遵循項布局規則。)TOAST 表和序列的格式與普通表一樣。

在下面解釋中,假定一個字節包含 8 個位。 另外,項(item)指的是存儲在一個頁面裡的獨立數據值。 在一個表裡,一個項是一個行;在一個索引裡,一個項是一條索引記錄。

Table 51-1顯示一個頁面的基本布局。每個頁面有五個部分。

Table 51-1. 樣例頁面布局

描述
PageHeaderData20 字節長。包含關于頁面的一般信息,包括自由空間指針。
ItemPointerData一個記錄(偏移量,長度)配對對的數組,指向實際項。
Free space(自由空間)未分配的空間。所有新行都從這裡分配,通常是從結尾開始。
Items(項)實際的項本身。
Special Space(特殊空間)索引訪問模式相關的數據。不同的索引訪問方式存放不同的數據。在普通表中為空。

每個頁面的頭20個字節組成頁頭(PageHeaderData)。它的格式在 Table 51-2 裡詳細介紹。 頭兩個字節處理與 WAL 相關的東西。 然後跟著三個 2 字節的整數字段(pd_lowerpd_upper,和 pd_special)。 這些字段分別表示與未分配空間開頭的字節偏移,與未分配空間結尾的字節偏移,以及到特殊空間開頭的字節偏移。

Table 51-2. PageHeaderData 布局

字段類型長度描述
pd_lsnXLogRecPtr8 字節LSN: xlog 最後一個字節的下一個字節
pd_suiStartUpID4 字節最後修改的啟動標識(SUI:StartUpID)(目前它只用于堆 AM)
pd_lowerLocationIndex2 字節到自由空間開頭的偏移量。
pd_upperLocationIndex2 字節到自由空間結尾的偏移量。
pd_specialLocationIndex2 字節到特殊空間開頭的偏移量。
pd_pagesize_versionuint162 字節頁面大小和布局版本號信息。

所有細節都可以在 src/include/storage/bufpage.h 裡找到。

特殊空間是在頁面末尾的區域,它在頁面初始化的時候分配,包含與某種訪問方式相關的信息。 頁頭的最後兩個字節,pd_pagesize_version, 保存頁面大小和一個版本指示器。從 PostgreSQL 7.3 開始, 版本數是 1;以前的版本使用版本號 0。(基本的頁面布局以及頭格式沒有變化,但是堆行頭的布局改變了。) 頁面大小通常只用作交叉檢查;在一次安裝中沒有大于一個頁面尺寸的支持。

在頁頭後面是項標識符(ItemIdData),每個需要四個字節。 一個項標識符包含一個到項開頭的字節偏移量,它自己以字節計的長度, 以及一個屬性位的集合,這些屬性位影響它的解釋。 新的項標識符根據需要從未分配空間的開頭分配。 項標識符的數目可以通過查看 pd_lower 來判斷,在分配新標識符的時候會遞增。 因為一個項標識符在其釋放前絕對不會移動,所以它的索引可以用于長時間地引用一個項, 即使該項本身因為壓縮自由空間在頁面內部進行了移動也如此。實際上,PostgreSQL 創建的每個指向項的指針(ItemPointer,也叫做 CTID)都由一個頁號和一個項標識符的索引組成。

項本身存儲在從未分配空間末尾開始從後向前分配的空間裡。它們的實際 結構因表包含的內容不同而不同。表和序列都使用一種叫做 HeapTupleHeaderData 的結構,在下面描述。

最後一段是"特殊段",它可以包含任何訪問方法想存放的東西。 普通表並不使用這個段(通過設置 pd_special 來指示,以平衡頁面大小)。

所有表行都用同樣方法構造。它們有一個定長的頭(在大多數機器上佔據23個字節), 後面跟著一個可選的 null 位圖,一個可選的對象 ID 字段,以及用戶數據。 頭在 Table 51-3 裡詳細描述。實際用戶數據(行的字段)從 t_hoff 標識的偏移量開始, 它必須是該平台的 MAXALIGN 距離的倍數。null 位圖只有在 t_infomask 裡面的 HEAP_HASNULL 位設置了的時候才出現。 如果它出現了,那麼它緊跟在定長頭後面,佔據足夠容納每個數據字段對應一個位的字節數(也就是說,總共 t_natts 位)。 在這個位列裡面,為 1 的位表示非空,而為 0 的位表示空。 如果沒有出現這個位圖,那麼所有數據字段都假設為非空的。 對象 ID 只有在設置了 t_infomask 裡面的 HEAP_HASOID 位的時候才出現。 如果出現,它正好出現在 t_hoff 範圍之前。 如果需要補齊 t_hoff,使之成為 MAXALIGN 的倍數,那麼這些填充將出現在 null 位圖和度相 ID 之間。 (這樣也保證了對象 ID 得到恰當的對齊。)

Table 51-3. HeapTupleHeaderData 布局

字段類型長度描述
t_xminTransactionId4 字節插入 XID 戳記
t_cminCommandId4 字節插入 CID 戳記(和 t_xmax 重疊)
t_xmaxTransactionId4 字節刪除 XID 戳記
t_cmaxCommandId4 字節刪除 CID 戳記(與 t_xvac 重疊)
t_xvacTransactionId4 字節用于 VACUUM 操作移動行版本的 XID
t_ctidItemPointerData6 字節這個或者新行的當前 ID
t_nattsint162 字節字段數目
t_infomaskuint162 字節各種標志
t_hoffuint81 字節到用戶數據的偏移量

所有細節都可以在 src/include/access/htup.h 中找到。

對具體數據的解釋只能在從其它表中獲取的信息的情況下進行, 這些信息大多數在 pg_attribute 裡。 尤其是 attlen 字段和 attalign 字段。 我們沒有辦法直接獲取某個字段,除非它們是定寬並且沒有 NULL 的。 所有這些復雜的操作都封裝在函數 heap_getattrfastgetattrheap_getsysattr 裡。

要讀取數據的話,你需要輪流檢查每個字段。首先根據 null 位圖檢查該字段是否為 NULL。 如果是,那麼跳到下一個字段。然後保證你的對齊是正確的。 如果字段是一個定寬字段,那麼所有字節都簡單地放在那裡。 如果它是一個變長字段(attlen == -1), 那麼它就會更加復雜一些,它會使用變長結構 varattrib。 根據標志地不同,數據可能是內聯的,也可能是壓縮的或者是在其它表中(TOAST)。