以下文章來源于資料庫架構之美,作者資料庫架構之美
我們知道postgresql資料庫通過資料多版本實作mvcc,pg又沒有undo段,老版本的資料元組直接存放在資料頁面中,這樣帶來的問題就是舊元組需要不斷地進行清理以釋放空間,這也是資料庫膨脹的根本原因。本文簡單介紹一下postgresql資料庫的元組、頁面的結構以及索引查找流程。
元組結構
元組,也叫tuple,這個叫法是很學術的叫法,但是現在資料庫中一般叫行或者記錄。下面是元組的結構:
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;
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! */
#define FIELDNO_HEAPTUPLEHEADERDATA_INFOMASK2 2
uint16 t_infomask2; /* number of attributes + various flags */
#define FIELDNO_HEAPTUPLEHEADERDATA_INFOMASK 3
uint16 t_infomask; /* various flag bits, see below */
#define FIELDNO_HEAPTUPLEHEADERDATA_HOFF 4
uint8 t_hoff; /* sizeof header incl. bitmap, padding */
/* ^ - 23 bytes - ^ */
#define FIELDNO_HEAPTUPLEHEADERDATA_BITS 5
bits8 t_bits[FLEXIBLE_ARRAY_MEMBER]; /* bitmap of NULLs */
/* MORE DATA FOLLOWS AT END OF STRUCT */
};

t_xmin:代表插入此元組的事務xid;
t_xmax:代表更新或者删除此元組的事務xid,如果該元組插入後未進行更新或者删除,t_xmax=0;
t_cid:command id,代表在目前事務中,已經執行過多少條sql,例如執行第一條sql時cid=0,執行第二條sql時cid=1;
t_ctid:儲存着指向自身或者新元組的元組辨別(tid),由兩個數字組成,第一個數字代表實體塊号,或者叫頁面号,第二個數字代表元組号。在元組更新後tid指向新版本的元組,否則指向自己,這樣其實就形成了新舊元組之間的“元組鍊”,這個鍊在元組查找和定位上起着重要作用。
了解了元組結構,再簡單了解下元組更新和删除過程。
更新過程
上圖中左邊是一條新插入的元組,可以看到元組是xid=100的事務插入的,沒有進行更新,是以t_xmax=0,同時t_ctid指向自己,0号頁面的第一号元組。右圖是發生xid=101的事務更新該元組後的狀态,更新在pg裡相當于插入一條新元組,原來的元組的t_xmax變為了更新這條事務的xid=101,同時t_ctid指針指向了新插入的元組(0,2),0号頁面第二号元組,第二号元組的t_xmin=101(插入該元組的xid),t_ctid=(0,2),沒有發生更新,指向自己。
删除過程
上圖代表該元組被xid=102的事務删除,将t_xmax設定為删除事務的xid,t_ctid指向自己。
頁面結構
下面再來看看頁面的結構
從上圖可以看到,頁面包括三種類型的資料
1.header data:資料頭是page生成的時候随之産生的,由pageHeaderData定義結構,24個位元組長,包含了page的相關資訊,下面是資料結構:
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;
pd_lsn: 存儲最近改變該頁面的xlog位置。
pd_checksum:存儲頁面校驗和。
pd_lower,pd_upper:pd_lower指向行指針(line pointer)的尾部,pd_upper指向最後那個元組。
pd_special: 索引頁面中使用,它指向特殊空間的開頭。
2.line pointer:行指針,四位元組,每一條元組會有一個行指針指向真實元組位置。
3.heap tuple:存放真實的元組資料,注意元組是從頁面的尾部向前堆積的,元組和行指針之間的是資料頁的空閑空間。
索引查找
看了頁面和元組結構,再看看索引的結構。
以上圖為例,索引的資料包含兩部分(key=xxx,TID=(block=xxx,offset=xxx)),key表示真實資料,tid代表指向資料行的指針,具體block代表頁面号,offset代表行偏移量,指向資料頁面的line pointer,比如執行下面的查詢語句
select * from tbl where id=1000;
key=1000,根據key值在索引中找到tid為5号頁面的1号元組,再通過一号元組行指針找到元組1,檢查元組1的t_ctid字段,發現指向了新的元組2,于是定位到真實元組資料2。