天天看點

WiredTiger的事務實作詳解一、多文檔事務的基本概念二、事務與複制集以及存儲引擎之間的關系三、WiredTiger事務的實作原理四、WiredTiger事務過程五、WiredTiger的事務隔離六、WiredTiger的事務日志七、總結

文章目錄

  • 一、多文檔事務的基本概念
    • 1、多文檔事務
    • 2、多文檔分布式事務
  • 二、事務與複制集以及存儲引擎之間的關系
    • 1、事務與複制集
    • 2、事務與存儲引擎
  • 三、WiredTiger事務的實作原理
    • 1、WT事務的構造
    • 2、WT的多版本并發控制
    • 3、WT事務snapshot
    • 4、全局事務管理器
    • 5、事務ID
  • 四、WiredTiger事務過程
  • 五、WiredTiger的事務隔離
    • 1、Read-Uncommited
    • 2、Read-Commited
    • 3、Snapshot- Isolation
  • 六、WiredTiger的事務日志
    • 1、日志格式
    • 2、WAL與日志寫并發
  • 七、總結

  WiredTiger從被MongoDB收購到成為MongoDB的預設存儲引擎的一年半得到了迅猛的發展,也逐漸被外部熟知。WiredTiger(以下簡稱WT)是一個優秀的單機資料庫存儲引擎,它擁有諸多的特性,既支援BTree索引,也支援LSM Tree索引,支援行存儲和列存儲,實作ACID級别事務、支援大到4G的記錄等。WT的産生不是因為這些特性,而是和計算機發展的現狀息息相關。

  現代計算機近20年來CPU的計算能力和記憶體容量飛速發展,但磁盤的通路速度并沒有得到相應的提高,WT就是在這樣的一個情況下研發出來,它設計了充分利用CPU并行計算的記憶體模型的無鎖并行架構,使得WT引擎在多核CPU上的表現優于其他存儲引擎。針對磁盤存儲特性,WT實作了一套基于BLOCK/Extent的友好的磁盤通路算法,使得WT在資料壓縮和磁盤I/O通路上優勢明顯。實作了基于snapshot技術的ACID事務,snapshot技術大大簡化了WT的事務模型,摒棄了傳統的事務鎖隔離又同時能保證事務的ACID。WT根據現代記憶體容量特性實作了一種基于Hazard Pointer 的LRU cache模型,充分利用了記憶體容量的同時又能擁有很高的事務讀寫并發。

  在本文中,我們主要針對WT引擎的事務來展開分析,來看看它的事務是如何實作的。說到資料庫事務,必然先要對事務這個概念和ACID簡單的介紹。

一、多文檔事務的基本概念

1、多文檔事務

  多文檔事務,可以了解為關系型資料庫的多行事務。在關系型的事務支援中,大家幾乎無一例外支援同一事務内操作的原子性,即要麼全部送出,要麼全部復原。這個同一事務内可以有多個操作,針對于多個表,或者是同一個表内的多行資料。

2、多文檔分布式事務

  傳統的關系型資料庫的事務是針對單節點的,而分布式系統有多個節點,一個事務裡可能會操作多個節點。

二、事務與複制集以及存儲引擎之間的關系

1、事務與複制集

  複制集配置下,MongoDB 整個事務在送出時,會記錄一條 oplog(oplog 是一個普通的文檔,是以目前版本裡事務的修改加起來不能超過文檔大小 16MB的限制),包含事務裡所有的操作,備節點拉取oplog,并在本地重放事務操作。事務 oplog 包含了事務操作的 lsid,txnNumber,以及事務内所有的記錄檔(applyOps字段)。

2、事務與存儲引擎

  WiredTiger 很早就支援事務,在 3.x 版本裡,MongoDB 就通過 WiredTiger 事務,來保證一條修改操作,對資料、索引、oplog 三者修改的原子性。但實際上 MongoDB 經過多個版本的疊代,才提供了事務接口,核心難點就是時序問題。

  MongoDB 通過 oplog 時間戳來辨別全局順序,而 WiredTiger 通過内部的事務ID來辨別全局順序,在實作上,2者沒有任何關聯。這就導緻在并發情況下, MongoDB 看到的事務送出順序與 WiredTiger 看到的事務送出順序不一緻。

  為解決這個問題,WiredTier 3.0 引入事務時間戳(transaction timestamp)機制,應用程式可以通過 WT_SESSION::timestamp_transaction 接口顯式的給 WiredTiger 事務配置設定 commit timestmap,然後就可以實作指定時間戳讀(read “as of” a timestamp)。有了 read “as of” a timestamp 特性後,在重放 oplog 時,備節點上的讀就不會再跟重放 oplog 有沖突了,不會因重放 oplog 而阻塞讀請求,這是4.0版本一個巨大的提升。

/*
 * __wt_txn_visible --
 *  Can the current transaction see the given ID / timestamp?
 */
static inline bool
__wt_txn_visible(
    WT_SESSION_IMPL *session, uint64_t id, const wt_timestamp_t *timestamp)
{
    if (!__txn_visible_id(session, id))
        return (false);

    /* Transactions read their writes, regardless of timestamps. */
    if (F_ISSET(&session->txn, WT_TXN_HAS_ID) && id == session->txn.id)
        return (true);

#ifdef HAVE_TIMESTAMPS
    {
    WT_TXN *txn = &session->txn;

    /* Timestamp check. */
    if (!F_ISSET(txn, WT_TXN_HAS_TS_READ) || timestamp == NULL)
        return (true);

    return (__wt_timestamp_cmp(timestamp, &txn->read_timestamp) <= 0);
    }
#else
    WT_UNUSED(timestamp);
    return (true);
#endif
}
           

  從上面的代碼可以看到,在引入事務時間戳之後,在可見性判斷時,還會額外檢查時間戳,上層讀取時指定了時間戳讀,則隻能看到該時間戳以前的資料。而 MongoDB 在送出事務時,會将 oplog 時間戳跟事務關聯,進而達到 MongoDB Server 層時序與 WiredTiger 層時序一緻的目的。

三、WiredTiger事務的實作原理

1、WT事務的構造

  知道了基本的事務概念和ACID後,來看看WT引擎是怎麼來實作事務和ACID的。要了解實作先要知道它的事務的構造和使用相關的技術,WT在實作事務的時使用主要是使用了三個技術:snapshot(事務快照)、MVCC (多版本并發控制)和redo log(重做日志),為了實作這三個技術,它還定義了一個基于這三個技術的事務對象和全局事務管理器。事務對象描述如下:

wt_transaction{
	transaction_id:    本次事務的全局唯一的ID,用于标示事務修改資料的版本号
	snapshot_object:   目前事務開始或者操作時刻其他正在執行且并未送出的事務集合,用于事務隔離
	operation_array:   本次事務中已執行的操作清單,用于事務復原。
	redo_log_buf:      記錄檔緩沖區。用于事務送出後的持久化
	State:             事務目前狀态
}
           

2、WT的多版本并發控制

  WT中的MVCC是基于key/value中value值的連結清單,這個連結清單單元中存儲有當先版本操作的事務ID和操作修改後的值。描述如下:

wt_mvcc{
	transaction_id:    本次修改事務的ID	
	value:             本次修改後的值
}
           

  WT中的資料修改都是在這個連結清單中進行append操作,每次對值做修改都是append到連結清單頭上,每次讀取值的時候讀是從連結清單頭根據值對應的修改事務transaction_id和本次讀事務的snapshot來判斷是否可讀,如果不可讀,向連結清單尾方向移動,直到找到讀事務能都的資料版本。樣例如下:

WiredTiger的事務實作詳解一、多文檔事務的基本概念二、事務與複制集以及存儲引擎之間的關系三、WiredTiger事務的實作原理四、WiredTiger事務過程五、WiredTiger的事務隔離六、WiredTiger的事務日志七、總結

  上圖中,事務T0發生的時刻最早,T5發生的時刻最晚。T1/T2/T4是對記錄做了修改。那麼在mvcc list當中就會增加3個版本的資料,分别是11/12/14。如果事務都是基于snapshot級别的隔離,T0隻能看到T0之前送出的值10,讀事務T3通路記錄時它能看到的值是11,T5讀事務在通路記錄時,由于T4未送出,它也隻能看到11這個版本的值。這就是WT 的MVCC基本原理。

3、WT事務snapshot

  上面多次提及事務的snapshot【提供可見性】,那到底什麼是事務的snapshot呢?其實就是事務開始或者進行操作之前對整個WT引擎内部正在執行或者将要執行的事務進行一次截屏,儲存當時整個引擎所有事務的狀态,确定哪些事務是對自己見的,哪些事務都自己是不可見。說白了就是一些列事務ID區間。WT引擎整個事務并發區間示意圖如下:

WiredTiger的事務實作詳解一、多文檔事務的基本概念二、事務與複制集以及存儲引擎之間的關系三、WiredTiger事務的實作原理四、WiredTiger事務過程五、WiredTiger的事務隔離六、WiredTiger的事務日志七、總結

  WT引擎中的snapshot_oject是有一個最小執行事務snap_min、一個最大事務snap max和一個處于[snap_min, snap_max]區間之中所有正在執行的寫事務序列組成。如果上圖在T6時刻對系統中的事務做一次snapshot,那麼産生的:

snapshot_object = {
    snap_min=T1,
    snap_max=T5,
	snap_array={T1, T4, T5},
};
           

那麼T6能通路的事務修改有兩個區間:所有小于T1事務的修改[0, T1)和[snap_min,snap_max]區間已經送出的事務T2的修改。換句話說,凡是出現在snap_array中或者事務ID大于snap_max的事務的修改對事務T6是不可見的。如果T1在建立snapshot之後送出了,T6也是不能通路到T1的修改。這個就是snapshot方式隔離的基本原理。

4、全局事務管理器

WiredTiger的事務實作詳解一、多文檔事務的基本概念二、事務與複制集以及存儲引擎之間的關系三、WiredTiger事務的實作原理四、WiredTiger事務過程五、WiredTiger的事務隔離六、WiredTiger的事務日志七、總結
WiredTiger的事務實作詳解一、多文檔事務的基本概念二、事務與複制集以及存儲引擎之間的關系三、WiredTiger事務的實作原理四、WiredTiger事務過程五、WiredTiger的事務隔離六、WiredTiger的事務日志七、總結
WiredTiger的事務實作詳解一、多文檔事務的基本概念二、事務與複制集以及存儲引擎之間的關系三、WiredTiger事務的實作原理四、WiredTiger事務過程五、WiredTiger的事務隔離六、WiredTiger的事務日志七、總結

5、事務ID

  從WT引擎建立事務snapshot的過程中現在可以确定,snapshot的對象是有寫操作的事務,純讀事務是不會被snapshot的,因為snapshot的目的是隔離mvcc list中的記錄,通過MVCC中value的事務ID與讀事務的snapshot進行版本讀取,與讀事務本身的ID是沒有關系。在WT引擎中,開啟事務時,引擎會将一個WT_TNX_NONE( = 0)的事務ID設定給開啟的事務,當它第一次對事務進行寫時,會在資料修改前通過全局事務管理器中的current_id來配置設定一個全局唯一的事務ID。這個過程也是通過CPU的CAS_ADD原子操作完成的無鎖過程。

四、WiredTiger事務過程

  一般事務是兩個階段:事務執行和事務送出。在事務執行前,我們需要先建立事務對象并開啟它,然後才開始執行,如果執行遇到沖突和或者執行失敗,我們需要復原事務(rollback)。如果執行都正常完成,最後隻需要送出(commit)它即可。從上面的描述可以知道事務過程有:建立開啟、執行、送出和復原。那麼從這幾個過程中來分析WT是怎麼實作這幾個過程的。

WiredTiger的事務實作詳解一、多文檔事務的基本概念二、事務與複制集以及存儲引擎之間的關系三、WiredTiger事務的實作原理四、WiredTiger事務過程五、WiredTiger的事務隔離六、WiredTiger的事務日志七、總結
WiredTiger的事務實作詳解一、多文檔事務的基本概念二、事務與複制集以及存儲引擎之間的關系三、WiredTiger事務的實作原理四、WiredTiger事務過程五、WiredTiger的事務隔離六、WiredTiger的事務日志七、總結
WiredTiger的事務實作詳解一、多文檔事務的基本概念二、事務與複制集以及存儲引擎之間的關系三、WiredTiger事務的實作原理四、WiredTiger事務過程五、WiredTiger的事務隔離六、WiredTiger的事務日志七、總結
WiredTiger的事務實作詳解一、多文檔事務的基本概念二、事務與複制集以及存儲引擎之間的關系三、WiredTiger事務的實作原理四、WiredTiger事務過程五、WiredTiger的事務隔離六、WiredTiger的事務日志七、總結

五、WiredTiger的事務隔離

  傳統的資料庫事務隔離分為:Read-Uncommited(未送出讀)、Read-Commited(送出讀)、Repeatable-Read(可重複讀)和Serializable(串行化),WT引擎并沒有按照傳統的事務隔離實作這四個等級,而是基于snapshot的特點實作了自己的Read-Uncommited、Read-Commited和一種叫做snapshot-Isolation(快照隔離)的事務隔離方式。在WT中不管是選用的是那種事務隔離方式,它都是基于系統中執行事務的快照截屏來實作的。那來看看WT是怎麼實作上面三種方式的。

WiredTiger的事務實作詳解一、多文檔事務的基本概念二、事務與複制集以及存儲引擎之間的關系三、WiredTiger事務的實作原理四、WiredTiger事務過程五、WiredTiger的事務隔離六、WiredTiger的事務日志七、總結

1、Read-Uncommited

  Read-Uncommited(未送出讀)隔離方式的事務在讀取資料時總是讀取到系統中最新的修改,哪怕是這個修改事務還沒有送出一樣讀取,這其實就是一種髒讀。WT引擎在實作這個隔方式時,就是将事務對象中的snap_object.snap_array置為空即可,那麼在讀取MVCC list中的版本值時,總是讀取到MVCC list連結清單頭上的第一個版本資料。舉例說明,在上圖中,如果T0/T3/T5的事務隔離級别設定成Read-Uncommited的話,那麼T1/T3/T5在T5時刻之後讀取系統的值時,讀取到的都是14。一般資料庫不會設定成這種隔離方式,它違反了事務的ACID特性。可能在一些注重性能且對髒讀不敏感的場景會采用,例如網頁cache。

2、Read-Commited

  Read-Commited(送出讀)隔離方式的事務在讀取資料時總是讀取到系統中最新送出的資料修改,這個修改事務一定是送出狀态。這種隔離級别可能在一個長事務多次讀取一個值的時候前後讀到的值可能不一樣,這就是經常提到的“幻象讀”。在WT引擎實作Read-Commited隔離方式就是事務在執行每個操作前都對系統中的事務做一次截屏,然後在這個截屏上做讀寫。還是來看上圖,T5事務在T4事務送出之前它進行讀取前做事務:

snapshot={
	snap_min=T2,
	snap_max=T4,
	snap_array={T2,T4},
};
           

在讀取MVCC list時,12和14修個對應的事務T2/T4都出現在snap_array中,隻能再向前讀取11,11是T1的修改,而且T1 沒有出現在snap_array,說明T1已經送出,那麼就傳回11這個值給T5。

  之後事務T2送出,T5在它送出之後再次讀取這個值,會再做一次:

snapshot={
	snap_min=T4,
    snap_max=T4,
    snap_array={T4},
};
           

這時在讀取MVCC list中的版本時,就會讀取到最新的送出修改12。

3、Snapshot- Isolation

  Snapshot-Isolation(快照隔離)隔離方式是讀事務開始時看到的最後送出的值版本修改,這個值在整個讀事務執行過程隻會看到這個版本,不管這個值在這個讀事務執行過程被其他事務修改了幾次,這種隔離方式不會出現“幻象讀”。WT在實作這個隔離方式很簡單,在事務開始時對系統中正在執行的事務做一個snapshot,這個snapshot一直沿用到事務送出或者復原。還是來看圖5,T5事務在開始時,對系統中的執行的寫事務做:

snapshot={
	snap_min=T2,
	snap_max=T4,
	snap_array={T2,T4}
};
           

那麼在他讀取值時讀取到的是11。即使是T2完成了送出,但T5的snapshot執行過程不會更新,T5讀取到的依然是11。這種隔離方式的寫比較特殊,就是如果有對事務看不見的資料修改,那麼本事務嘗試修改這個資料時會失敗復原,這樣做的目的是防止忽略不可見的資料修改。

  通過上面對三種事務隔離方式的分析,WT并沒有使用傳統的事務獨占鎖和共享通路鎖來保證事務隔離,而是通過對系統中寫事務的snapshot截屏來實作。這樣做的目的是在保證事務隔離的情況下又能提高系統事務并發的能力。

六、WiredTiger的事務日志

  通過上面的分析可以知道WT在事務的修改都是在記憶體中完成的,事務送出時也不會将修改的MVCC list當中的資料刷入磁盤,那麼WT是怎麼保證事務送出的結果永久儲存呢?WT引擎在保證事務的持久可靠問題上是通過redo log(重做記錄檔)的方式來實作的,在本文的事務執行和事務送出階段都有提到寫記錄檔。WT的記錄檔是一種基于K/V操作的邏輯日志,它的日志不是基于btree page的實體日志。說的通俗點就是将修改資料的動作記錄下來,例如:插入一個key= 10,value= 20的動作記錄在成:

{
Operation = insert,(動作)
Key = 10,
Value = 20
};
           
WiredTiger的事務實作詳解一、多文檔事務的基本概念二、事務與複制集以及存儲引擎之間的關系三、WiredTiger事務的實作原理四、WiredTiger事務過程五、WiredTiger的事務隔離六、WiredTiger的事務日志七、總結

1、日志格式

  WT引擎的記錄檔對象(以下簡稱為logrec)對應的是送出的事務,事務的每個操作被記錄成一個logop對象,一個logrec包含多個logop,logrec是一個通過精密序列化事務操作動作和參數得到的一個二進制buffer,這個buffer的資料是通過事務和操作類型來确定其格式的。

  WT中的日志分為4類:分别是建立checkpoint的記錄檔(LOGREC_CHECKPOINT)、普通事務記錄檔(LOGREC_COMMIT)、btree page同步刷盤的記錄檔(LOGREC_FILE_SYNC)和提供給引擎外部使用的日志(LOGREC_MESSAGE)。這裡介紹和執行事務密切先關的LOGREC_COMMIT,這類日志裡面由根據K/V的操作方式分為:LOG_PUT(增加或者修改K/V操作)、LOG_REMOVE(單KEY删除操作)和範圍删除日志,這幾種操作都會記錄操作時的key,根據操作方式填寫不同的其他參數,例如:update更新操作,就需要将value填上。除此之外,日志對象還會攜帶btree的索引檔案ID、送出事務的ID等,整個logrec和logop的關系結構圖如下:

WiredTiger的事務實作詳解一、多文檔事務的基本概念二、事務與複制集以及存儲引擎之間的關系三、WiredTiger事務的實作原理四、WiredTiger事務過程五、WiredTiger的事務隔離六、WiredTiger的事務日志七、總結

2、WAL與日志寫并發

WiredTiger的事務實作詳解一、多文檔事務的基本概念二、事務與複制集以及存儲引擎之間的關系三、WiredTiger事務的實作原理四、WiredTiger事務過程五、WiredTiger的事務隔離六、WiredTiger的事務日志七、總結
WiredTiger的事務實作詳解一、多文檔事務的基本概念二、事務與複制集以及存儲引擎之間的關系三、WiredTiger事務的實作原理四、WiredTiger事務過程五、WiredTiger的事務隔離六、WiredTiger的事務日志七、總結
WiredTiger的事務實作詳解一、多文檔事務的基本概念二、事務與複制集以及存儲引擎之間的關系三、WiredTiger事務的實作原理四、WiredTiger事務過程五、WiredTiger的事務隔離六、WiredTiger的事務日志七、總結

七、總結

  可以說WT在事務的實作上另辟蹊徑,整個事務系統的實作沒有用繁雜的事務鎖,而是使用snapshot和MVCC這兩個技術輕松的而實作了事務的ACID,這種實作也大大提高了事務執行的并發性。除此之外,WT在各個事務子產品的實作多采用無鎖并發,充分利用CPU的多核能力來減少資源競争和I/O操作,可以說WT在實作上是有很大創新的。

繼續閱讀