PostgreSQL MVCC快照機制淺析

husthxd發表於2019-01-16

快照隔離是資料庫實現併發控制的一種常用技術.本節簡單探討了快照理論模型、PostgreSQL中的工程實踐以及由此推廣到Postgres-XL的可能改進思路等.

一、快照模型

快照,顧名思義表示某個時間點的狀態.在日常生活中,我們使用相機拍攝,拍下來的畫面可以理解為拍攝物件的快照;與此類似,連線資料庫執行操作(如查詢)時資料庫的狀態也可以視為快照.
為了方便討論,假定資料庫事務隔離級別為READ COMMITTED,考慮以下場景:
快照#1

假設在時間點T4執行操作OP,在T4獲取資料庫狀態快照:
其中,TR1/TR4為已完結事務,TR2/TR3/TR5是正在進行中的事務,TR6是在T4後發生的事務.顯然,TR1和TR4事務對資料庫的修改對操作OP可見;TR6是未來發生的事務,對操作OP不可見;TR2/TR3/TR5正在進行中,同樣也不可見.
就此場景而言,理論上我們只需要知道”拍攝”快照的時間T4,就可以控制資料庫變化的可見性了,簡單而言就是:在此時間前完結的事務,可見,否則不可見.

二、PostgreSQL工程實踐

按上一小節的介紹,可以看到快照天生具有時間屬性,但在PG中,事務號並沒有使用時間戳等資訊而是使用自然數列(1,2,…,n,…).
考慮以下場景(簡單起見,假設無論是隻讀還是修改事務,均分配事務號):
快照#2

XID=315的事務執行操作OP,獲取資料庫快照,與上一個場景型別類似,TR1和TR4事務執行的資料庫修改對操作OP可見;TR6是未來發生的事務,對操作OP不可見;TR2/TR3/TR5正在進行中,同樣也不可見.理論上來說,我們只需要知道該查詢的事務號(如315)就可以控制可見性了.但在工程實踐上,PostgreSQL並沒有簡單的使用事務號作為快照,而是使用了最後一個已結束的事務號+1(即snapshot中的xmax)作為歷史和未來的分界:
1、大於等於xmax的,屬於未來事務,不可見;
2、小於xmax的,已結束事務可見,進行中事務不可見。
 出於效能的考慮,為了進一步區分小於xmax的事務,引入了xmin和xip_list:
 1)小於xmin的事務,視為已結束事務,可見;
 2)大於等於xmin小於xmax,進行中的事務,不可見,否則可見。
仍以上述場景為例(詳見下圖):
快照#3

最後已結束事務為200,最早仍活躍事務為125,則:
xmax = 200 + 1 = 201
xmin = 125
snapshot = 125 : 201 : 140

獲取快照
通過函式txid_current_snapshot()可獲取當前的快照資訊:


11:05:16 (xdb@[local]:5432)testdb=# select txid_current_snapshot();
 txid_current_snapshot 
-----------------------
 2404:2404:
(1 row)
11:24:11 (xdb@[local]:5432)testdb=#

輸出格式為xmin : xmax : xip_list
其中:
xmin:最早仍活躍的事務ID(以下簡稱XID),早於此XID的事務要麼被提交併可見,要麼回滾要麼丟棄。
xmax:最後已完結事務(COMMITTED/ABORTED)的事務ID + 1。
xip_list:在”拍攝”快照時仍進行中的事務ID。該列表包含xmin和xmax之間的活動事務ID。
總結一下,簡單來說,對於給定的XID:
XID ∈ [1,xmin),過去的事務,對此快照均可見;
XID ∈ [xmin,xmax),不考慮子事務的情況,仍處於IN_PROGRESS狀態的,不可見;COMMITED狀態,可見;ABORTED狀態,不可見;
XID ∈ [xmax,∞),未來的事務,對此快照均不可見;

資料結構
在原始碼中SnapshotData結構體定義如下:


typedef struct SnapshotData
{
    //判斷tuple是否可見的函式
    SnapshotSatisfiesFunc satisfies;    /* tuple test function */
    //XID ∈ [2,min)是可見的 
    TransactionId xmin;         /* all XID < xmin are visible to me */
    //XID ∈ [xmax,∞)是不可見的
    TransactionId xmax;         /* all XID >= xmax are invisible to me */
    /*
     * For normal MVCC snapshot this contains the all xact IDs that are in
     * progress, unless the snapshot was taken during recovery in which case
     * it's empty. For historic MVCC snapshots, the meaning is inverted, i.e.
     * it contains *committed* transactions between xmin and xmax.
     * 對於普通的MVCC快照,xip儲存了所有正在進行中的XIDs,除非在恢復期間產生的快照(這時候陣列為空)
     * 對於歷史MVCC快照,意義相反,即它包含xmin和xmax之間的*已提交*事務。
     *
     * note: all ids in xip[] satisfy xmin <= xip[i] < xmax
     * 注意: 所有在xip陣列中的XIDs滿足xmin <= xip[i] < xmax
     */
    TransactionId *xip;
    ...
}

xmin/xmax/xip分別對應txid_current_snapshot()函式輸出的xmin/xmin/xip_list.

三、Postgres-XL的改進

XL的意思是:eXtensible Lattice,可以擴充套件的格子,即將PostgreSQL應用在多機器上的分散式資料庫。Postgres-XL 是一個完全滿足ACID的、開源的、可方便進行水平擴充套件的、多租戶安全的、基於PostgreSQL的資料庫解決方案。
其體系結構如下圖所示:
PG-XL

Postgres-XL沿襲了PostgreSQL的事務模型,仍使用自然數列作為事務號XID,因此存在作為中心點的全域性事物管理器的GTM(Global Transaction Monitor),負責處理事務ID和快照。由於GTM作為中心的存在,一是容易成為整個資料庫叢集的效能瓶頸,二是可能會出現中心單點錯誤的問題,因此GTM所負責的事務ID和快照管理是否可以通過各個節點自行處理呢?
不考慮廣義相對論,在地球上,時間是絕對公平和單調”遞增”的,這個屬性與自然數列類似,因此我們可以考慮利用本機的時間戳作為事務號生成的輸入,保證事務號按順序的單調遞增和唯一性.
時間戳的定義(來自百度百科):

時間戳是指格林威治時間1970年01月01日00時00分00秒(北京時間1970年01月01日08時00分00秒)起至現在的總毫秒數。通俗的講, 時間戳是一份能夠表示一份資料在一個特定時間點已經存在的完整的可驗證的資料。 它的提出主要是為使用者提供一份電子證據, 以證明使用者的某些資料的產生時間。 在實際應用上, 它可以使用在包括電子商務、 金融活動的各個方面, 尤其可以用來支撐公開金鑰基礎設施的 “不可否認” 服務。
一般時間戳使用無符號64位的整型(uint64)表示

事務ID
按Postgres-XL的體系結構,假設叢集中有N個節點,每個節點都部署Coordinator協調器,可以想到的可能改進思路是在獲取事務號時,由Coordinator獲取本地時間戳,通過一定的機制(HLC演算法/TSO服務/分散式協議…)保證該時間戳的順序性和唯一性,然後把該時間戳通過演算法(最簡單的演算法是對2^32進行求餘數)轉換為PG的事務號.

快照管理
快照管理與獲取事務ID類似,在某個節點需要獲取快照時,該節點的Coordinator根據時間戳獲取快照中的xmax,然後通過協調其他各個節點的Coordinator獲取xmin/xip_list,組成最終的snapshot.

誠然,上述思路只是理論上的推導,在工程實踐仍有非常多的細節(比如各個節點之間的時間同步/如何減少網路開銷等等)需要考慮.

四、參考資料

Mvcc Unmasked - Bruce Momjian
Postgres-XL
分散式系統的時間

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/6906/viewspace-2562652/,如需轉載,請註明出處,否則將追究法律責任。

相關文章