[譯] 混亂世界中的穩定:Postgres 如何使事務原子化

混亂世界中的穩定:Postgres 如何使事務原子化

原子性( 「ACID」 特性)聲明,對於一系列的數據庫操做,要麼全部操做一塊兒提交,要麼所有回滾;不容許中間狀態存在。對於那些須要去適應混亂的現實世界的代碼來講,簡直是天賜良物。前端

那些改變數據並繼續惡化下去的故障將被取代,這些改變會被恢復。當你在處理着百萬級請求的時候,可能會由於間歇性的問題致使鏈接的斷斷續續或者出現一些其它的突發狀況,從而致使一些不便,但不會打亂你的數據。react

衆所周知 Postgres 的實現中提供了強大的事務語義化。雖然我已經用了好幾年,可是有些東西我歷來沒有真正理解。Postgres 有着穩定出色的工做表現,讓我安心把它當成一個黑盒子 -- 驚人地好用,可是內部的機制倒是鮮爲人知的。android

這篇文章是探索 Postgres 如何保持它的事務及原子性提交,和一些可讓咱們深刻理解其內部機制的關鍵概念[1]ios

管理併發訪問

假如你創建了一個簡易數據庫,這個數據庫讀寫硬盤上的 CSV 文件。當只有一個客戶端發起請求時,它會打開文件,讀取信息並寫入信息。一切運行很是完美,有一天,你決定強化你的數據庫,給它加入更加複雜的新特性 - 多客戶端支持!git

不幸地是,當兩個客戶端同時試圖去操做數據的時候,新功能當即被出現的問題所困擾。當一個 CSV 文件正在被一個客戶端讀取,修改,和寫入數據的時候,若是另外一個客戶端也嘗試去作一樣的事情,這個時候就會發生衝突。github

客戶端之間的資源爭奪會致使數據丟失。這是併發訪問出現的常見問題。能夠經過引入併發控制來解決。曾經有過許多原始解決方案。例如咱們讓來訪者帶上獨佔鎖去讀寫文件,或者咱們能夠強制讓全部訪問都須要經過流控制點,從而實現同一時間只能運行其一。可是這些方法不只運行緩慢,並且因爲不能縱向擴展,從而使數據庫不能徹底支持 ACID 特性。現代數據庫有一個完美的解決辦法,MVCC (多版本並行控制系統)。數據庫

在 MVCC,語句在事務裏面執行。它會建立一個新版本,而不會直接覆寫數據。有需求的客戶端仍然可使用原始的數據,但新的數據會被隱藏起來直到事務被提交。這樣客戶端之間就不存在直接爭奪的狀況,數據也再也不面臨重寫並且能夠安全地被保存。編程

事務開始執行的時候,數據庫會生成一個此刻數據庫狀態的快照。在數據庫的每個事務都會以串行的順序執行,經過一個全局鎖來保證每次只有一個事務可以提交或者停止操做。快照完美體現了兩個事務之間的數據庫狀態。後端

爲了不被刪除或隱藏的行數據不斷地堆積,數據庫最後將通過一個 vacuum 程序(或在某些狀況下,帶有歧義查詢的 「microvacuums」 隊列)來清理淘汰數據,可是隻能在數據再也不被其它快照使用的時候才能進行。數組

讓咱們看下 Postgres 如何使用 MVCC 管理併發的狀況。

事務、元組和快照

這是 Postgres 用於實現事務的結構(來自 proc.c):

typedef struct PGXACT
{
    TransactionId xid;   /* 當前由程序執行的頂級事務 ID 
                          * 若是正在執行且 ID 被賦值;
                          * 不然是無效事務 ID */

    TransactionId xmin;  /* 正在運行最小的 XID,
                          *  除了 LAZY VACUUM:
                          * vacuum 不移除因 xid >= xmin
                          * 而被刪除的元組 */

    ...
} PGXACT;複製代碼

事務是以 xid(或 「xact」 ID)來標記。這是 Postgres 的優化,當事務開始更改數據的時候,Postgres 會賦值一個 xid 給它。由於只有那個時候,其它程序才須要開始追蹤它的改變。只讀操做能夠直接執行,不須要分配 xid

當這個事務開始運行的時候,xmin 立刻被設置爲運行事務中 xid 最小的值。Vacuum 進程計算數據的最低邊界,使它們保持 xmin 是全部事務中的最小值。

生命週期感知的元組

在 Postgres,行數據經常與元組有關。當 Postgres 使用像 B 樹通用的查找結構去快速檢索信息,索引並無存儲一個元素的完整數據或其中任意的可見信息。相反,他們存儲能夠從物理存儲器(也稱爲「堆」)檢索特定行的 tid(元組 ID)。Postgres 經過 tid 做爲起始點,對堆進行掃描直到找到一個能知足當前快照的可見元組。

這是 Postgres 實現的堆元組(不是索引元組),以及它頭信息的結構( 來自 htup.h htup_details.h):

typedef struct HeapTupleData
{
    uint32          t_len;         /* *t_data 的長度 */
    ItemPointerData t_self;        /* SelfItemPointer */
    Oid             t_tableOid;    /* 惟一標識一個表 */
    HeapTupleHeader t_data;        /* -> 元組的頭部及數據 */
} HeapTupleData;

/* 相關的 HeapTupleData */
struct HeapTupleHeaderData
{
    HeapTupleFields t_heap;

    ...
}

/* 相關的 HeapTupleHeaderData */
typedef struct HeapTupleFields
{
    TransactionId t_xmin;        /* 插入 xact ID */
    TransactionId t_xmax;        /* 刪除或隱藏 xact ID */

    ...
} HeapTupleFields;複製代碼

像事務同樣,元組也會追蹤它本身的 xmin,可是這隻在特定的元組狀況下,例如它被記錄爲第一個事務,其中元組變可見。(即建立它的那個)。它還追蹤 xmax 做爲最後的一個的事務,其中元組是可見的(即刪除它的那個)[2]

可使用 xminxmax 來追蹤堆元組的生存期。雖然 xminxmax 是內部概念,可是他們能夠顯示任何 Postgres 表上被隱藏的列。經過名字顯示地選擇它們:

# SELECT *, xmin, xmax FROM names;

 id |   name   | xmin  | xmax
----+----------+-------+-------
  1 | Hyperion | 27926 | 27928
  2 | Endymion | 27927 |     0複製代碼

快照:xmin,xmax,和 xip

這是快照的實現結構 (來自 snapshot.h):

typedef struct SnapshotData
{
    /*
     * 如下字段僅用於 MVCC 快照,和在特定的快照。
     * (但 xmin 和 xmax 專門用於 HeapTupleSatisfiesDirty)
     * 
     *
     * 一個 MVCC 快照 永遠不可能見到 XIDs >= xmax 的事務。
     * 除了那些列表中的 snapshot,它會看到時間長的 XIDs 的內容。
     * 對於大多數的元組,xmin 被存儲起來是個優化的操做,這樣避免去搜索 XID 數組。
     * 
     */
    TransactionId xmin;            /* id小於xmin的全部事務更改在當前快照中可見 */
    TransactionId xmax;            /* id大於xmax的全部事務更改在當前快照中可見 */

    /*
     * 對於普通的 MVCC 快照,它包含了程序中全部的 xact IDs
     * 除非在它是空的狀況下被使用。
     * 對於歷史 MVCC 的快照, 這就是恰好相反, 即它包含了在 xmin 和 xmax 中已提交的事務。
     * 
     *
     * 注意: 全部在 xip[] 的 ids 都知足 xmin <= xip[i] < xmax
     */
    TransactionId *xip; /* 全部正在運行的事務的id列表 */
    uint32        xcnt; /* 正在運行的事務的計數 */

    ...
}複製代碼

快照的 xmin 計算方式和計算事務的相同(即在正在運行的事務中,xid 最低的事務),但用途卻不同。xmin 是數據可見的最低邊界。元組是被 xid < xmin 條件的事務所建立,對快照可見。

同時也有定義爲 xmax 的變量,它被設置爲最後一次提交事務的 xid + 1。xmax 是數據可見的上限;xid >= xmax 的事務對快照是不可見的。

最後,當快照被建立,它會定義一個 *xip 做爲存儲全部事務 xid 的數組。*xip 存在是由於即便 xmin 被設定爲可見邊界,可能有一些已經提交的事務的 xid 大於 xmin,但也存在 xmin 大於一些處於執行階段的事務的 xid

咱們但願任何 xid > xmin 的事務提交結果都是可見的,但事實上它們被隱藏了。快照建立的時候,*xip 存儲的有效事務清單能夠幫助咱們辨別各事務身份。

事務是對數據庫進行操做,快照是爲了抓捕數據庫一瞬間的信息。

開啓事務

當你執行 BEGIN 語句,儘管 Postgres 對於一些經常使用的操做會有相應優化,但它會盡量地推遲更多開銷比較大的操做。舉個例子,一個新的事務在開始修改數據以前,咱們不會給它分配 xid。這樣作能夠減小在其餘地方追蹤它的花費。

新的事務也不會當即使用快照。當事務運行第一個查詢,exec_simple_query (postgres.c)纔會將其入棧。甚至一個簡單的 SELECT 1; 語句也會觸發:

static void
exec_simple_query(const char *query_string)
{
    ...

    /*
     * 若是解析/計劃須要,則設置一個快照
     */
    if (analyze_requires_snapshot(parsetree))
    {
        PushActiveSnapshot(GetTransactionSnapshot());
        snapshot_set = true;
    }

    ...
}複製代碼

建立新快照是程序真正開始加載的起始點。這是 GetSnapshotData (procarray.c):

Snapshot
GetSnapshotData(Snapshot snapshot)
{
    /* xmax 老是等於 latestCompletedXid + 1 */
    xmax = ShmemVariableCache->latestCompletedXid;
    Assert(TransactionIdIsNormal(xmax));
    TransactionIdAdvance(xmax);

    ...

    snapshot->xmax = xmax;
}複製代碼

這個函數作了不少初始化的工做,但像咱們談到的,它最主要的工做就是設置快照的 xminxmax,和 *xip。其中最簡單的就是設置 xmax,它能夠從 Postmaster 管理的共享存儲器中檢索出來。每一個提交的事務都會通知 Postmaster,和 latestCompletedXid 將會被更新,若是 xid 高於當前 xid 的值(稍後將詳細介紹)。

須要注意的是,最後的 xid 自增是由函數實行的。由於在 Postgres 裏面,事務的 IDs 是被容許包裝,因此並非單純的自增那麼簡單。一個事務 ID 是被定義爲一個無符號32位整數(來自 c.h):

typedef uint32 TransactionId;複製代碼

儘管 xid 是看狀況來分配的(上文提過,讀取數據時是不須要它的),可是系統大量的吞吐量很容易就達到32位的邊界,因此係統須要根據需求將 xid 序列進行「重置」。這是由一些預處理器處理的(在 transam.h):

#define InvalidTransactionId ((TransactionId) 0)
#define BootstrapTransactionId ((TransactionId) 1)
#define FrozenTransactionId ((TransactionId) 2)
#define FirstNormalTransactionId ((TransactionId) 3)

...

/* 提早一個事務ID變量, 直接操做 */
#define TransactionIdAdvance(dest) \
    do { \
        (dest)++; \
        if ((dest) < FirstNormalTransactionId) \
            (dest) = FirstNormalTransactionId; \
    } while(0)複製代碼

最初的幾個 ID 被保留做爲特殊標識符,因此咱們通常跳過它,從 3 開始。

回到 GetSnapshotData 裏,經過迭代全部正在執行的事務咱們能夠獲得 xminxip (回顧快照中它們的做用):

/*
 * 循環 procArray 查看 xid,xmin,和 subxids。  
 * 目的是獲得全部 active xids,找到最低的 xmin,和試着去記錄 subxids。
 * 
 */
for (index = 0; index < numProcs; index++)
{
    volatile PGXACT *pgxact = &allPgXact[pgprocno];
    TransactionId xid;
    xid = pgxact->xmin; /* fetch just once */

    /*
     * 若是事務中沒有被賦值的 XID,咱們能夠跳過;
     * 對於 sub-XIDs 也同理。若是 XID >= xmax,咱們也能夠跳過它;
     * 這樣的事務被認爲(任何 sub-XIDs 都將 >= xmax)。
     * 
     */
    if (!TransactionIdIsNormal(xid)
        || !NormalTransactionIdPrecedes(xid, xmax))
        continue;

    if (NormalTransactionIdPrecedes(xid, xmin))
        xmin = xid;

    /* 添加 XID 到快照中。 */
    snapshot->xip[count++] = xid;

    ...
}

...

snapshot->xmin = xmin;複製代碼

提交事務

事務經過 CommitTransaction (在 xact.c)被提交。函數很是複雜,下面代碼是函數比較重要部分:

static void
CommitTransaction(void)
{
    ...

    /*
     * 咱們須要去 pg_xact 標記 XIDs 來表示已提交。做爲
     * 已穩定提交的標記。
     */
    latestXid = RecordTransactionCommit();

    /*
     * 讓其餘知道沒有其餘事務在程序中。
     * 須要注意的是,這個操做必須在釋放鎖以前
     * 和記錄事務提交以前完成。
     */
    ProcArrayEndTransaction(MyProc, latestXid);

    ...
}複製代碼

持久性和 WAL

Postgres 是徹底圍繞着持久性的概念設計的。這樣即便像在外力摧毀或功率損耗的狀況下,已提交的事務也保持原有的狀態。像許多優秀的系統,Postgres 使用預寫式日誌( WAL,或 「xlog」)去實現穩定。全部的更改被記錄進磁盤,甚至像宕機這種事情,Postgres 會搜尋 WAL,而後從新恢復沒有寫進數據文件的更改記錄。

從上面 RecordTransactionCommit 的片斷代碼中,將事務的狀態更改到 WAL:

static TransactionId
RecordTransactionCommit(void)
{
    bool markXidCommitted = TransactionIdIsValid(xid);

    /*
     * 若是目前咱們尚未指派 XID,那咱們就不能再指派,也不能
     * 寫入提交記錄
     */
    if (!markXidCommitted)
    {
        ...
    } else {
        XactLogCommitRecord(xactStopTimestamp,
                            nchildren, children, nrels, rels,
                            nmsgs, invalMessages,
                            RelcacheInitFileInval, forceSyncCommit,
                            MyXactFlags,
                            InvalidTransactionId /* plain commit */ );

        ....
    }

    if ((wrote_xlog && markXidCommitted &&
         synchronous_commit > SYNCHRONOUS_COMMIT_OFF) ||
        forceSyncCommit || nrels > 0)
    {
        XLogFlush(XactLastRecEnd);

        /*
         * 若是咱們寫入一個有關提交的記錄,那麼可能更新 CLOG
         */
        if (markXidCommitted)
            TransactionIdCommitTree(xid, nchildren, children);
    }

    ...
}複製代碼

commit log

伴隨着 WAL,Postgres 也有一個commit log(或者叫 「clog」 和 「pg_xact」)。這個記錄都保存事務提交痕跡,不管最後事務提交與否。上面的 TransactionIdCommitTree 實現了這個功能 - 首先會嘗試把一系列的信息寫入 WAL,而後 TransactionIdCommitTree 會在 commit log 中改成「已提交」。

雖然 commit log 也被稱爲「日誌」,但實際上它是一個提交狀態的位圖,在共享內存和在磁盤上的進行拆分。
在現代編程中不多出現這麼簡約的例子,事務的狀態能夠僅使用二個字節來記錄,咱們能每字節存儲四個事務,或者每一個標準 8k 頁面存儲 32758。

來自 clog.hclog.c:

#define TRANSACTION_STATUS_IN_PROGRESS 0x00
#define TRANSACTION_STATUS_COMMITTED 0x01
#define TRANSACTION_STATUS_ABORTED 0x02
#define TRANSACTION_STATUS_SUB_COMMITTED 0x03

#define CLOG_BITS_PER_XACT 2
#define CLOG_XACTS_PER_BYTE 4
#define CLOG_XACTS_PER_PAGE (BLCKSZ * CLOG_XACTS_PER_BYTE)複製代碼

優化的規模

穩定性當然重要,但性能表現也是一個 Postgres 哲學中的核心元素。如果事務從不賦值 xid,Postgres 就會跳過 WAL 和提交日誌。如果事務被停止,咱們仍然會把它停止的狀態寫進 WAL 和 commit log,但不要急着立刻去刷新(同步),由於實際上即便系統崩潰了,咱們也不會丟失任何信息。在故障恢復期間,Postgres 會提示沒有標記的事務,認爲它們被停止了。

防護性編程

TransactionIdCommitTree (在 transam.c, 和 它的 實現 TransactionIdSetTreeStatusclog.c) 提交信息呈樹狀,由於用戶接下來可能還有二次提交。我不會詳細介紹二次提交,由於二次提交使 TransactionIdCommitTree 不能保證原子性,每個二次提交都單獨提交,而父進程被記錄爲最後一次操做。當 Postgres 在宕機中恢復數據時,二次提交記錄不被認爲是提交的(即便它們已經一樣被標記)直到父記錄被讀取和確認提交。

這再一次體現原子性;系統能夠成功記錄任何二次提交的記錄,但在它寫入父進程以前就崩潰了。

就像clog.c 所實現的:

/*
 * 將提交日誌中的事務目錄的最終狀態記錄到單個頁面上全部目錄上。
 * 原子只出如今這個頁面。
 *
 * 其餘的 API 與 TransactionIdSetTreeStatus() 相同。
 */
static void
TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
                           TransactionId *subxids, XidStatus status,
                           XLogRecPtr lsn, int pageno)
{
    ...

    LWLockAcquire(CLogControlLock, LW_EXCLUSIVE);

    /*
     * 不管什麼狀況,都設置事務的 id。
     *
     * 若是咱們在寫的時候在這個頁面上更新超過一個 xid,
     * 咱們可能發現有些位轉到了磁盤,有些則不會。
     * 若是咱們在更新頁面的時候提交了一個破壞原子性的最高級 xid,
     * 那麼在咱們標記最高級的提交以前咱們先提交 subxids。
     * 
     */
    if (TransactionIdIsValid(xid))
    {
        /* Subtransactions first, if needed ... */
        if (status == TRANSACTION_STATUS_COMMITTED)
        {
            for (i = 0; i < nsubxids; i++)
            {
                Assert(ClogCtl->shared->page_number[slotno] == TransactionIdToPage(subxids[i]));
                TransactionIdSetStatusBit(subxids[i],
                                          TRANSACTION_STATUS_SUB_COMMITTED,
                                          lsn, slotno);
            }
        }

        /* ... 而後是主事務 */
        TransactionIdSetStatusBit(xid, status, lsn, slotno);
    }

    ...

    LWLockRelease(CLogControlLock);
}複製代碼

經過共用存儲器來標記完成的事務

當事務被記錄到提交日誌,向系統其餘部分進行提示是一種安全行爲。這發生在上面的 CommitTransaction 的第二次調用(在 procarray.c):

void
ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid)
{
    /*
     * 當清除咱們的 XID時,咱們必須鎖住 ProcArrayLock
     * 這樣當別人設置快照的時候,運行的事務已全被清空了。
     * 看討論
     * src/backend/access/transam/README.
     */
    if (LWLockConditionalAcquire(ProcArrayLock, LW_EXCLUSIVE))
    {
        ProcArrayEndTransactionInternal(proc, pgxact, latestXid);
        LWLockRelease(ProcArrayLock);
    }

    ...
}

static inline void
ProcArrayEndTransactionInternal(PGPROC *proc, PGXACT *pgxact,
                                TransactionId latestXid)
{
    ... 

    /* 也是在持鎖的狀況下提早全局 latestCompletedXid */
    if (TransactionIdPrecedes(ShmemVariableCache->latestCompletedXid,
                              latestXid))
        ShmemVariableCache->latestCompletedXid = latestXid;
}複製代碼

你可能想知道什麼是「proc array」。不像其餘的服務進程,Postgres 沒有使用線程,而是使用一個分岔模型的程序來操做併發機制。當它接受一個新鏈接,Postmaster 分開一個新服務器進程(postmaster.c)。使用 PGPROC 數據結構來表示服務器進程 (proc.h),和有效的程序的集合均可以在共用存儲器追蹤到,這就是「proc array」。

如今還記得咱們如何建立一個快照並把它的 xmax 設置爲 latestCompletedXid + 1?經過把全局共用存儲器中的 latestCompletedXid 賦值給剛提交的事務的 xid,咱們把它的結果對全部從這一刻開始,任何服務器進程的新快照均可見。

看如下獲取鎖和釋放鎖所調用的 LWLockConditionalAcquireLWLockRelease。大多數時候,Postgres 很是樂意讓程序都並行工做,可是有一些地方須要得到鎖來避免爭奪,而這就是須要用到它們的時候。在文章的開頭,咱們提到了在 Postgres 的事務是如何按順序依次提交或停止的。ProcArrayEndTransaction 須要獨佔鎖以便於當它更新 latestCompletedXid 的時候不被別的程序打擾。

響應客戶端

在整個流程中,客戶端在它的事務被確認以前會同步地等待。部分原子性是虛構數據庫標記事務爲提交,這不是不可能的。不少地方均可能發生故障,可是若是出現了故障,客戶端會找出它而後去重試或解決問題。

檢查可見性

咱們以前說過如何將可見的信息存儲在堆元組。heapgettup (heapam.c) 是負責掃描堆,看看裏面有沒有符合快照可見性的標準:

static void
heapgettup(HeapScanDesc scan,
           ScanDirection dir,
           int nkeys,
           ScanKey key)
{
    ...

    /*
     * 預先掃描直到找到符合的元組
     * 
     */
    lpp = PageGetItemId(dp, lineoff);
    for (;;)
    {
        /*
         * if current tuple qualifies, return it.
         */
        valid = HeapTupleSatisfiesVisibility(tuple,
                                             snapshot,
                                             scan->rs_cbuf);

        if (valid)
        {
            return;
        }

        ++lpp;            /* 這個頁面的itemId數組向前移動一個索引 */
        ++lineoff;
    }

    ...
}複製代碼

HeapTupleSatisfiesVisibility 是一個預處理宏,它將會調用 「satisfies」 功能像 HeapTupleSatisfiesMVCC (tqual.c):

bool
HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
                       Buffer buffer)
{
    ...

    else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple)))
        SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
                    HeapTupleHeaderGetRawXmin(tuple));

    ...

    /* xmax transaction committed */

    return false;
}複製代碼

TransactionIdDidCommit (來自 transam.c):

bool /* true if given transaction committed */
TransactionIdDidCommit(TransactionId transactionId)
{
    XidStatus xidstatus;

    xidstatus = TransactionLogFetch(transactionId);

    /*
     *  若是該事務標記提交,那就提交
     */
    if (xidstatus == TRANSACTION_STATUS_COMMITTED)
        return true;

    ...
}複製代碼

進一步探究 TransactionLogFetch 將揭示了它的工做原理。它從給出的事務 ID 計算提交日誌中的位置,並經過它獲取該事務中的提交狀態。事務提交是否用於幫助肯定元組的可見性。

關鍵在於一致性,提交日誌被認爲是提交狀態的標準(還有擴展性,可見性)[3]。不管 Postgres 是否在數小時前成功提交了事務,或服務器剛剛從崩潰的前幾秒中恢復,一樣的信息都會被返回。

提示位

在從數據可見檢查返回以前,從上面的 HeapTupleSatisfiesMVCC 上再作一件事:

SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
            HeapTupleHeaderGetRawXmin(tuple));複製代碼

覈對提交日誌去查看元組的 xminxmax 事務是否被提交是一個昂貴的操做。避免每次都要訪問它,Postgres 會爲被掃描的堆元組設置一個特別的提交狀態標記(被稱爲「提示位」)。後續操做能夠檢查堆提示位並保存到提交日誌。

盒子的黑牆

當我在數據庫運行一個事務:

BEGIN;

SELECT * FROM users

INSERT INTO users (email) VALUES ('brandur@example.com')
RETURNING *;

COMMIT;複製代碼

我不會中止思考其中發生什麼。我獲得一個強大的高級抽象(以 SQL 形式),我知道這樣作是可靠的,如咱們所看到的,Postgres 在底層作好了全部繁雜的細節工做。好的軟件就是一個黑盒子,而 Postgres 是特別黑的那種(儘管有可訪問的內部的接口)。

感謝 Peter Geoghegan 耐心地回答了我全部業餘問題,有關 Postgres 事務和快照,和給予我尋找相關源碼的指引。

  • 1 提幾句建議:Postgres 源碼是很是龐大,因此我略寫了一些細節,讓讀者更容易消化。因爲 Postgres 還在持續開發中,引用的代碼可能會過期。
  • 2 讀者可能會注意到,xmin and xmax 對於跟蹤元組的建立和刪除是很是適合,可是它們還不足夠去處理更新操做。爲了達到目的,目前我不會談論更新操做是如何實現的。
  • 3 注意,提交日誌最終將會被截斷,但只能在快照的 xmin 範圍以外,因此在對 WAL 檢查以前,須要先對可見性進行檢查。

混亂世界中的穩定:Postgres 如何使事務變得原子化發表於舊金山在 2017 年 8 月 16 日。

在推特上能夠找到我 @brandur
請在 Hacker News 上發表你的看法。
若是文章有錯,請 pull request.


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOSReact前端後端產品設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索