天天看點

如何從零學習PostgreSQL Page結構

導讀: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結構

如何從零學習PostgreSQL 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;
           

具體的長度和描述也都有詳細說明:

如何從零學習PostgreSQL Page結構

簡單來說,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不一樣:

如何從零學習PostgreSQL Page結構

prune_xid表示這個page上最早删除或者修改tuple的事務id,在vacuum操作的時候會用到。(pg沒有undo,舊的資料也在page中,用vacuum來清理)

2.3 linp結構(行指針)

如何從零學習PostgreSQL Page結構

lp_off是tuple的開始的偏移量;lp_flags是标志位;lp_len記錄了tuple的長度。

如何從零學習PostgreSQL Page結構

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)           

也有對應的長度和描述的相詳細說明:

如何從零學習PostgreSQL Page結構

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,插入資料

如何從零學習PostgreSQL Page結構

這裡可以看到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

如何從零學習PostgreSQL Page結構

我們來看一行記錄,可以看到它是第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

如何從零學習PostgreSQL Page結構

我們删除一行tuple可以看到prune_xid有了值,為559,這個559就是删除這個tuple的事務id(目前最早的删除或更改了tuple的事務id)

如何從零學習PostgreSQL Page結構

同樣,我們可以看到lp為1的這個tuple的t_xmax為559,這裡就是删除這行tuple的事務id。

PostgreSQL Page的實體結構相比Oracle的資料塊來說簡單很多了,源代碼開放也便于學習和研究,pg是個很好很強大的資料庫,值得好好學習。

原文釋出時間為:2018-12-9

本文作者: 李亮

本文來自雲栖社群合作夥伴“

資料和雲

”,了解相關資訊可以關注“OraNews”微信公衆号