天天看點

PostgreSQL的元組、頁面結構及索引查找原理

以下文章來源于資料庫架構之美,作者資料庫架構之美

我們知道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 */
};           
PostgreSQL的元組、頁面結構及索引查找原理

t_xmin:代表插入此元組的事務xid;

t_xmax:代表更新或者删除此元組的事務xid,如果該元組插入後未進行更新或者删除,t_xmax=0;

t_cid:command id,代表在目前事務中,已經執行過多少條sql,例如執行第一條sql時cid=0,執行第二條sql時cid=1;

t_ctid:儲存着指向自身或者新元組的元組辨別(tid),由兩個數字組成,第一個數字代表實體塊号,或者叫頁面号,第二個數字代表元組号。在元組更新後tid指向新版本的元組,否則指向自己,這樣其實就形成了新舊元組之間的“元組鍊”,這個鍊在元組查找和定位上起着重要作用。

了解了元組結構,再簡單了解下元組更新和删除過程。

更新過程

PostgreSQL的元組、頁面結構及索引查找原理

上圖中左邊是一條新插入的元組,可以看到元組是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),沒有發生更新,指向自己。

删除過程

PostgreSQL的元組、頁面結構及索引查找原理

上圖代表該元組被xid=102的事務删除,将t_xmax設定為删除事務的xid,t_ctid指向自己。

頁面結構

下面再來看看頁面的結構

PostgreSQL的元組、頁面結構及索引查找原理

從上圖可以看到,頁面包括三種類型的資料

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:存放真實的元組資料,注意元組是從頁面的尾部向前堆積的,元組和行指針之間的是資料頁的空閑空間。

索引查找

看了頁面和元組結構,再看看索引的結構。

PostgreSQL的元組、頁面結構及索引查找原理

以上圖為例,索引的資料包含兩部分(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。