| PostgreSQL 7.4 文檔 | ||||
|---|---|---|---|---|
| Prev | Fast Backward | Fast Forward | Next | |
本章提供一個 PostgreSQL 的表和索引所使用的頁面格式的概述。 (索引訪問模式不需要使用這些頁面格式。目前,所有索引方法的確都使用這個基本格式, 但保留在索引元數據頁裡的數據通常並不準確地遵循項布局規則。)TOAST 表和序列的格式與普通表一樣。
在下面解釋中,假定一個字節包含 8 個位。 另外,項(item)指的是存儲在一個頁面裡的獨立數據值。 在一個表裡,一個項是一個行;在一個索引裡,一個項是一條索引記錄。
Table 51-1顯示一個頁面的基本布局。每個頁面有五個部分。
Table 51-1. 樣例頁面布局
| 項 | 描述 |
|---|---|
| PageHeaderData | 20 字節長。包含關于頁面的一般信息,包括自由空間指針。 |
| ItemPointerData | 一個記錄(偏移量,長度)配對對的數組,指向實際項。 |
| Free space(自由空間) | 未分配的空間。所有新行都從這裡分配,通常是從結尾開始。 |
| Items(項) | 實際的項本身。 |
| Special Space(特殊空間) | 索引訪問模式相關的數據。不同的索引訪問方式存放不同的數據。在普通表中為空。 |
每個頁面的頭20個字節組成頁頭(PageHeaderData)。它的格式在 Table 51-2 裡詳細介紹。 頭兩個字節處理與 WAL 相關的東西。 然後跟著三個 2 字節的整數字段(pd_lower,pd_upper,和 pd_special)。 這些字段分別表示與未分配空間開頭的字節偏移,與未分配空間結尾的字節偏移,以及到特殊空間開頭的字節偏移。
Table 51-2. PageHeaderData 布局
| 字段 | 類型 | 長度 | 描述 |
|---|---|---|---|
| pd_lsn | XLogRecPtr | 8 字節 | LSN: xlog 最後一個字節的下一個字節 |
| pd_sui | StartUpID | 4 字節 | 最後修改的啟動標識(SUI:StartUpID)(目前它只用于堆 AM) |
| pd_lower | LocationIndex | 2 字節 | 到自由空間開頭的偏移量。 |
| pd_upper | LocationIndex | 2 字節 | 到自由空間結尾的偏移量。 |
| pd_special | LocationIndex | 2 字節 | 到特殊空間開頭的偏移量。 |
| pd_pagesize_version | uint16 | 2 字節 | 頁面大小和布局版本號信息。 |
所有細節都可以在 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_xmin | TransactionId | 4 字節 | 插入 XID 戳記 |
| t_cmin | CommandId | 4 字節 | 插入 CID 戳記(和 t_xmax 重疊) |
| t_xmax | TransactionId | 4 字節 | 刪除 XID 戳記 |
| t_cmax | CommandId | 4 字節 | 刪除 CID 戳記(與 t_xvac 重疊) |
| t_xvac | TransactionId | 4 字節 | 用于 VACUUM 操作移動行版本的 XID |
| t_ctid | ItemPointerData | 6 字節 | 這個或者新行的當前 ID |
| t_natts | int16 | 2 字節 | 字段數目 |
| t_infomask | uint16 | 2 字節 | 各種標志 |
| t_hoff | uint8 | 1 字節 | 到用戶數據的偏移量 |
所有細節都可以在 src/include/access/htup.h 中找到。
對具體數據的解釋只能在從其它表中獲取的信息的情況下進行, 這些信息大多數在 pg_attribute 裡。 尤其是 attlen 字段和 attalign 字段。 我們沒有辦法直接獲取某個字段,除非它們是定寬並且沒有 NULL 的。 所有這些復雜的操作都封裝在函數 heap_getattr, fastgetattr 和 heap_getsysattr 裡。
要讀取數據的話,你需要輪流檢查每個字段。首先根據 null 位圖檢查該字段是否為 NULL。 如果是,那麼跳到下一個字段。然後保證你的對齊是正確的。 如果字段是一個定寬字段,那麼所有字節都簡單地放在那裡。 如果它是一個變長字段(attlen == -1), 那麼它就會更加復雜一些,它會使用變長結構 varattrib。 根據標志地不同,數據可能是內聯的,也可能是壓縮的或者是在其它表中(TOAST)。