如何從零學習PostgreSQL Page結構
作者 | 李亮,雲和恩墨西區交付工程師,長期服務於運營商、社保、銀行、醫院、公積金等行業,擅長資料庫備份恢復,升級遷移,效能優化,sql優化。
導讀:PostgreSQL 號稱是“世界上最先進的開源資料庫”(The world's most advanced open source database),在DB-Engines的排名中長期處於第四的位置,並且增長趨勢明顯。開源的優勢就是可以直接閱讀原始碼,本文通過原始碼結合pageinspect對pg的page結構進行解析和學習。
一、Page
pg中的page和Oracle中的資料塊一樣,指的是資料庫的塊,作業系統塊的整數倍個,預設是8K也就是兩個作業系統塊(4k的檔案系統塊)。這個大小在pg編譯安裝configure的時候通過--with-blocksize引數指定,單位是Kb。
二、Page的內部結構
2.1 page結構
2.2 PageHeaderData資料結構 (頁頭)
可以看到一個Page有 Pager header(頁頭),後面是linp(行指標),pd_lower和pd_upper分別是空閒空間的開始位置和結束位置;後面就是行資料(pg裡面的行就是tuple)和special空間。整個page的結構比Oracle的資料塊結構簡單多了。
typedef struct PageHeaderData
{
/* XXX LSN is member of *any* block, not only page-organized ones */
PageXLogRecPtr pd_lsn; /* LSN: next byte after last byte of xlog
* record for last change to this page */
uint16 pd_checksum; /* checksum */
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 */
LocationIndex pd_special; /* offset to start of special space */
uint16 pd_pagesize_version;
TransactionId pd_prune_xid; /* oldest prunable XID, or zero if none */
ItemIdData pd_linp[FLEXIBLE_ARRAY_MEMBER]; /* line pointer array */
} PageHeaderData;
具體的長度和描述也都有詳細說明:
Field | Type | Length | Description |
pd_lsn | PageXLogRecPtr | 8 bytes | LSN: next byte after last byte of WAL record for last change to this page |
pd_checksum | uint16 | 2 bytes | Page checksum |
pd_flags | uint16 | 2 bytes | Flag bits |
pd_lower | LocationIndex | 2 bytes | Offset to start of free space |
pd_upper | LocationIndex | 2 bytes | Offset to end of free space |
pd_special | LocationIndex | 2 bytes | Offset to start of special space |
pd_pagesize_version | uint16 | 2 bytes | Page size and layout version number information |
pd_prune_xid | TransactionId | 4 bytes | Oldest unpruned XMAX on page, or zero if none |
簡單來說,pd_lsn是指最後修改過這個page的lsn(log sequence number),這個和wal(write ahead log,同oracle redo)中記錄的lsn一致。資料落盤時redo必須先刷到wal,這個pd_lsn就記錄了最後data落盤時的相關redo的lsn。
pd_checksum是校驗和,在initdb初始化例項的時候通過-k引數指定開啟,預設是關閉的,initdb之後不能修改,它基於FNV-1a hash演算法,做了相應的更改。這個校驗和與Oracle的checksum一樣用於資料塊在讀入和寫出記憶體時的校驗。比如我們在記憶體中修改了一個資料塊,寫入到磁碟的時候,在記憶體裡面先計算好checksum,資料塊寫完後再計算一遍cheksum是否和之前在記憶體中的一致,確保整個寫出過程沒有出錯,保護資料結構不被破壞。
pd_flags有以下的值:
/*
* pd_flags contains the following flag bits. Undefined bits are initialized
* to zero and may be used in the future.
*
* PD_HAS_FREE_LINES is set if there are any LP_UNUSED line pointers before
* pd_lower. This should be considered a hint rather than the truth, since
* changes to it are not WAL-logged.
*
* PD_PAGE_FULL is set if an UPDATE doesn't find enough free space in the
* page for its new tuple version; this suggests that a prune is needed.
* Again, this is just a hint.
*/
#define PD_HAS_FREE_LINES 0x0001 /* are there any unused line pointers? */
#define PD_PAGE_FULL 0x0002 /* not enough free space for new tuple? */
#define PD_ALL_VISIBLE 0x0004 /* all tuples on page are visible to
* everyone */
#define PD_VALID_FLAG_BITS 0x0007 /* OR of all valid pd_flags bits */
pd_lower和pd_upper分別表示空閒空間起始位置和結束位置;pd_special在索引page才有效;pd_pagesize_version是page大小和page version的儲存位,在不同資料庫版本中,page version不一樣:
資料庫版本 | pd_pagesize_version | ||
<7.3 | 0 | ||
7.3 & 7.4 | 1 | ||
8.0 | 2 | ||
8.1 | 3 | ||
>8.3 | 4 |
prune_xid表示這個page上最早刪除或者修改tuple的事務id,在vacuum操作的時候會用到。(pg沒有undo,舊的資料也在page中,用vacuum來清理)
2.3 linp結構(行指標)
lp_off是tuple的開始的偏移量;lp_flags是標誌位;lp_len記錄了tuple的長度。
Field | Length | Description |
lp_off | 15 bits | offset to tuple |
lp_flags | 2 bits | State of iteam pointer |
lp_len | 15 bits | Byte length of tuple |
2.4 tuple header結構(行頭)
typedef struct HeapTupleFields { TransactionId t_xmin; /* inserting xact ID */ TransactionId t_xmax; /* deleting or locking xact ID */ union { CommandId t_cid; /* inserting or deleting command ID, or both */ TransactionId t_xvac; /* old-style VACUUM FULL xact ID */ } t_field3; } HeapTupleFields;
typedef struct DatumTupleFields { int32 datum_len_; /* varlena header (do not touch directly!) */ int32 datum_typmod; /* -1, or identifier of a record type */ Oid datum_typeid; /* composite type OID, or RECORDOID */
/* * Note: field ordering is chosen with thought that Oid might someday * widen to 64 bits. */ } DatumTupleFields;
struct HeapTupleHeaderData { union { HeapTupleFields t_heap; DatumTupleFields t_datum; } t_choice;
ItemPointerData t_ctid; /* current TID of this or newer tuple (or a * speculative insertion token) */ /* Fields below here must match MinimalTupleData! */ uint16 t_infomask2; /* number of attributes + various flags */ uint16 t_infomask; /* various flag bits, see below */ uint8 t_hoff; /* sizeof header incl. bitmap, padding */ /* ^ - 23 bytes - ^ */ bits8 t_bits[FLEXIBLE_ARRAY_MEMBER]; /* bitmap of NULLs */ /* MORE DATA FOLLOWS AT END OF STRUCT */ }; (*這部分程式碼在src/include/access/htup_details.h) |
也有對應的長度和描述的相詳細說明:
Field | Type | Length | Description |
t_xmin | TransactionId | 4 bytes | insert XID stamp |
t_xmax | TransactionId | 4 bytes | delete XID stamp |
t_cid | CommandId | 4 bytes | insert and/or delete CID stamp (overlays with t_xvac) |
t_xvac | TransactionId | 4 bytes | XID for VACUUM operation moving a row version |
t_ctid | ItemPointerData | 6 bytes | current TID of this or newer row version |
t_infomask2 | uint16 | 2 bytes | number of attributes, plus various flag bits |
t_infomask | uint16 | 2 bytes | various flag bits |
t_hoff | uint8 | 1 byte | offset to user data |
union是共享結構體,起作用的變數是最後一次賦值的成員。來看看tuple header的結構。
在HeapTupleFields中,t_xmin是插入這行tuple的事務id;t_xmax是刪除或者鎖住tuple的事務id;union結構中的t_cid是刪除或者插入這個tuple的命令id,也就是命令序號;t_xvac是以前格式的vacuum full用到的事務id。
在DatumTupleFields中,datum_len_ 指tuple的長度;datum_typmod是記錄的type;datum_typeid是記錄的id。
頁頭HeapTupleHeaderData包含了union結構體中的兩個變數HeapTupleFields和DatumTupleFields。t_ctid是tuple id,類似oracle的rowid,形式為(塊號,行號)。
t_infomask2 表示屬性和標誌位
t_infomask 是flag標誌位,具體值如下:
/* * information stored in t_infomask: */ #define HEAP_HASNULL 0x0001 /* has null attribute(s) */ #define HEAP_HASVARWIDTH 0x0002 /* has variable-width attribute(s) */ #define HEAP_HASEXTERNAL 0x0004 /* has external stored attribute(s) */ #define HEAP_HASOID 0x0008 /* has an object-id field */ #define HEAP_XMAX_KEYSHR_LOCK 0x0010 /* xmax is a key-shared locker */ #define HEAP_COMBOCID 0x0020 /* t_cid is a combo cid */ #define HEAP_XMAX_EXCL_LOCK 0x0040 /* xmax is exclusive locker */ #define HEAP_XMAX_LOCK_ONLY 0x0080 /* xmax, if valid, is only a locker */
/* xmax is a shared locker */ #define HEAP_XMAX_SHR_LOCK (HEAP_XMAX_EXCL_LOCK | HEAP_XMAX_KEYSHR_LOCK)
#define HEAP_LOCK_MASK (HEAP_XMAX_SHR_LOCK | HEAP_XMAX_EXCL_LOCK | \ HEAP_XMAX_KEYSHR_LOCK) #define HEAP_XMIN_COMMITTED 0x0100 /* t_xmin committed */ #define HEAP_XMIN_INVALID 0x0200 /* t_xmin invalid/aborted */ #define HEAP_XMIN_FROZEN (HEAP_XMIN_COMMITTED|HEAP_XMIN_INVALID) #define HEAP_XMAX_COMMITTED 0x0400 /* t_xmax committed */ #define HEAP_XMAX_INVALID 0x0800 /* t_xmax invalid/aborted */ #define HEAP_XMAX_IS_MULTI 0x1000 /* t_xmax is a MultiXactId */ #define HEAP_UPDATED 0x2000 /* this is UPDATEd version of row */ #define HEAP_MOVED_OFF 0x4000 /* moved to another place by pre-9.0 * VACUUM FULL; kept for binary * upgrade support */ #define HEAP_MOVED_IN 0x8000 /* moved from another place by pre-9.0 * VACUUM FULL; kept for binary * upgrade support */ #define HEAP_MOVED (HEAP_MOVED_OFF | HEAP_MOVED_IN)
#define HEAP_XACT_MASK 0xFFF0 /* visibility-related bits */ |
t_hoff表示tuple header的長度
t_bits記錄了tuple中null值的列
三、實驗
3.1 安裝pageinspect
它在原始碼的crontrib目錄下面
postgres@cs-> cd postgresql-10.4/contrib/pageinspect
make && make install postgres@cs-> make postgres@cs-> make install
create extension就好了 postgres@cs-> psql psql (10.4) Type "help" for help.
postgres=# CREATE EXTENSION pageinspect; CREATE EXTENSION
postgres=# \x Expanded display is on. postgres=# \dx List of installed extensions -[ RECORD 1 ]------------------------------------------------------ Name | pageinspect Version | 1.6 Schema | public Description | inspect the contents of database pages at a low level -[ RECORD 2 ]------------------------------------------------------ Name | plpgsql Version | 1.0 Schema | pg_catalog Description | PL/pgSQL procedural language |
3.2 建立建測試表t1,插入資料
這裡可以看到1000行資料用了6個資料塊來儲存(這裡資料塊從0開始),第6個資料塊包含了73條記錄(tuple)
3.3 Pageinspect檢視page
這裡我們通過兩個函式來檢視
page_header 可以看到頁頭的資料
heap_pageitems 可以看到具體tuple的資料
3.3.1 page_header
postgres=# \xExpanded display is on.postgres=# select * from page_header(get_raw_page('t1',0));-[ RECORD 1 ]--------lsn | 0/1671188checksum | 0flags | 0lower | 772upper | 784special | 8192pagesize | 8192version | 4prune_xid | 0postgres=# |
可以看到第0個page的pd_lsn為0/1671188,checksum和flags都是0,這裡沒有開啟checksum;tuple開始偏移是772(pd_lower),結束偏移是784(pd_upper),這個page是個表,所以它沒有special,我們看到的sepcial就是8192了;pagesize是8192就是8K,version是4,沒有需要清理的tuple,所以儲存需要清理的tuple的最早事務的id就是0(prune_xid)。
3.3.2 heap_page_items
我們來看一行記錄,可以看到它是第1行記錄(lp=1),tuple的開始偏移量8160(lp_off),tuple的長度是32 bytes(lp_len為32,這個tuple是第一個插入的tuple,所以lp_off+lp_len=8160+32=8192),這行記錄的插入事務id是557(t_min),和tuple的刪除事務id是0(tmax),這裡資料沒有被刪除,所以都是0。我們還可以看到t_ctid是(0,1),這裡表示這個tuple是這個page中第一個塊的第一條tuple;tinfomask2是2,t_infomask為2306,十六進位制就是 0x0902 ,這個我們可以根據上面提到的值去看看具體的含義,0x0902 = 0x0100 + 0x0800 +0x0002;tuple頭部結構(行頭)的長度是24(t_hoff),t_data就是16進位制儲存的真正的資料了。
3.4 刪除一行資料觀察prune_xid
我們刪除一行tuple可以看到prune_xid有了值,為559,這個559就是刪除這個tuple的事務id(當前最早的刪除或更改了tuple的事務id)
同樣,我們可以看到lp為1的這個tuple的t_xmax為559,這裡就是刪除這行tuple的事務id。
PostgreSQL Page的物理結構相比Oracle的資料塊來說簡單很多了,原始碼開放也便於學習和研究,pg是個很好很強大的資料庫,值得好好學習。
原創:李亮
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31556440/viewspace-2284731/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- PostgreSQL儲存引擎之page結構SQL儲存引擎
- PostgreSQL Page頁結構解析(1)-基礎SQL
- PostgreSQL Page頁結構解析(3)- 行資料SQL
- PostgreSQL Page頁結構解析(5)- B-Tree索引儲存結構#1SQL索引
- PostgreSQL Page頁結構解析(6)- B-Tree索引儲存結構#2SQL索引
- PostgreSQL Page頁結構解析(7)- B-Tree索引儲存結構#3SQL索引
- 從零開始JAVA資料結構學習筆記(一)Java資料結構筆記
- 【教程】如何從零開始構建深度學習專案?深度學習
- 從零學習SpringSecuritySpringGse
- PostgreSQL Page頁結構解析(2)- 頁頭和行資料指標SQL指標
- 從零開始學習如何部署程式碼
- 軟體測試如何從零開始學習
- 我是如何從零開始學習前端的前端
- 從零開始學Python—第六課:迴圈結構Python
- PostgreSQL Page頁結構解析(4)- 執行DML時表佔用空間解析SQL
- 從零開始學習機器學習機器學習
- 從零開始學習的朋友應該如何學習Linux技術?Linux
- PostgreSQL 資料庫學習 - 1.資料庫體系結構之儲存結構SQL資料庫
- 從零開始學習laravelLaravel
- 從零開始學習KafkaKafka
- PostgreSQL:物理結構SQL
- PostgreSQL:程式結構SQL
- 【深度學習】如何從零開始構建深度學習專案?這裡有一份詳細的教程深度學習
- PostgreSQL如何檢視page、index的詳細資訊SQLIndex
- 從零實現Vue的元件庫(零)- 基本結構以及構建工具Vue元件
- 從零學Python:第十五課-常用資料結構之字典Python資料結構
- 從零學Python:第十四課-常用資料結構之集合Python資料結構
- 從零開始機器學習機器學習
- 結構體學習結構體
- 手把手教你從零搭建深度學習專案(附連結)深度學習
- 從零開始學Python:第九課-常用資料結構之字串Python資料結構字串
- 最新【從零單排】系列流出,教你如何實現字典儲存結構
- 從零開始學機器學習——構建一個推薦web應用機器學習Web
- PostgreSQL:邏輯結構SQL
- PostgreSQL 目錄結構SQL
- 資料結構學習之樹結構資料結構
- 從零開始機器學習-03機器學習
- 從零開始機器學習--4機器學習