一、事務的概念
(一)什麼是事務?
事務四大特性:A(原子性)、C(一緻性)、I(隔離性)、D(持久性)
原子性:
原子性是指事務是一個不可分割的工作機關,事務中的操作要麼全部成功,要麼全部失
敗。
一緻性:
事務必須使資料庫從一個一緻性狀态變換到另外一個一緻性狀态。
隔離性:
事務的隔離性是多個使用者并發通路資料庫時,資料庫為每一個使用者開啟的事務,不能被 其他事務的操作資料所幹擾,多個并發事務之間要互相隔離。
持久性:
一個事務一旦被送出,它對資料庫中資料的改變就是永久性的,接下來即使資料庫發生 故障也不應該對其有任何影響。
(二)為什麼 MongoDB 需要事務
MongoDB 推薦文檔模型,使用嵌入文檔将不同資料放在一個文檔中,以下圖為例:

這個文檔是某位職場人資訊記錄,其中包含名字、職位、保險身份證号以及位址。位址 資訊就是一個嵌入文檔,包括城市街道和郵政編碼。
文檔模型将多種相關資料放到一個文檔當中,使得業務在使用的時候,修改文檔隻需要 支援單行事務,就可以對一個文檔中的不同字段同時進行修改。
但是在某些場景下,單行事務無法滿足業務需求,需要跨行事務。
例如:
1)大量多對多的關系
股票價格和交易詳情是大量多對多關系的常見場景。股票的每個交易詳情都會影響到股 票的價格變動,是以需要新寫一條交易記錄,然後同時并發更改股票價格。這兩個操作需要 在一個事務當中原子地進行。但由于這些交易詳情的資料過多,一個文檔限制為 16MB 無 法放置,是以這種場景需要進行跨表操作。
2)事件處理
在建立使用者的時候,需要在使用者表與事件表裡分别寫一條記錄,這樣應用中其他系統就 可以去處理建立使用者的事件。
3)業務操作記錄
比如某業務需要進行一個資料操作,同時要在一個日志表裡記錄資料操作,業務進行操 作和日志表記錄需要在一個事務當中,操作記錄需在另一個獨立的表中實施,此時兩個操作 需要是跨表進行,并且是原子操作。
以上三種場景單行事務無法滿足,但通過 MongoDB 跨行事務的功能可以得到有效解
決。
(三)MongoDB 對事務的支援
- 單行事務:原子更新一個文檔中的多個字段;
- 副本集的跨行事務(v4.0):在副本集上,跨多個文檔、多個表、多個 DB 的多個操作保 持原子性;
- 叢集的跨行事務(v4.2):在分片叢集上,跨多個文檔、多個表、多個 DB 的多個操作保 持原子性;
- 事務包含部分 DDL(v4.4):包括建立表和索引。
二、事務的使用
(一)MongoDB 單行事務
單行操作為什麼需要事務?單行事務并不隻是對一個資料文檔進行更新,也需要對多個 文檔進行更新。如上圖所示,将 MongoDB 寫入一個文檔需要五個步驟:
第一,單行事務對 People 表寫一條記錄,People 表裡面有兩個索引,一個是非唯一 索引的名字,另外一個是唯一索引,它是一個身份證号,Key 為 1 表明第一行記錄, Value 為記錄詳情;
第二,MongoDB 會添加一個_ID 字段,會将索引項寫到_ID 索引中,是以第二行 Index_ID 索引表裡也寫條記錄,它的 Key 就是一個_ID,Value 對應的就是資料表中行的 記錄;
第三,将索引項寫到使用者建立的索引當中。由于使用者創了兩個索引,是以需要先寫一個 唯一索引 Index_Ssn。接着需要寫一個非唯一索引,因為非唯一索引可能出現多個,是以 除了要把非唯一索引資料放進來,還需要将行号也放進來;
第四,唯一索引檢查 Key 是否重複,檢查 Ssn 索引表裡這個記錄是否已經存在,如果 存在的話則會報錯;
第五,寫入記錄檔到複制表,将寫操作同步到 Secondary 節點上。
單行操作存儲引擎支援事務的,這樣才能将多個操作放在一個事務中進行。
(二)MongoDB 副本集的跨行事務
當單行事務無法滿足業務需求時,則需要跨行事務(4.0 版本之後)。
上面為一個副本集架構圖,應用讀寫 Primary,寫的記錄會複制到 Secondary 存庫中, 事務的操作均為讀寫主庫。
事務參數:
ReadConcern: snapshot
WriteConcern: majority
ReadPreference: primary
跨行事務舉例:
上圖場景為某職場人離開一家公司加入一家新公司。
首先先開啟一個 Session,在 Session 上擷取員工 People 表與公司 Company 表。 随後 Session 開啟一個事務 ReadConcern: Snapshot,WriteConcern: Majority。
接着在員工表中确認員工身份證 ID 與資訊是否存在,如果存在則将相關資訊在員工表 與公司表中進行替換,将這兩個操作放到一個事務中,對事務進行送出。
以上就是一個較為常見的跨表事務操作場景,該操作也存在一定的限制:
限制一:事務最長生命周期:TransactionLifetimeLimitSeconds(60s); 限制二:(v4.0)所有寫操作的資料大小不超過 16MB(4.2 之後不再限制)。
生産環境的事務代碼,考慮寫沖突、網絡報錯:
以上圖為例,代碼兩個函數調用組成。
先看下面函數的調用,它通過循環運作事務,然後用 Try Except 捕捉操作來報錯, 報錯的識别錯誤中有個辨別一類錯誤碼 Error Label。
當這一類錯誤碼是 TransientTransactionError 時,意味着遇到一個寫沖突或者是請 求過程時發現網絡報錯,在這種報錯場景下,使用者往往可以通過重試解決。
上面的函數是一個事務的操作,通過 Session 開啟事務,然後往兩個表裡分别寫一條 記錄,在 Commit_transaction 時做 while 循環,目的是解決網絡請求報錯,這個請求報 錯是由于 MongoDB Commit 結束以後回包時網絡的報錯,導緻這個包沒有被用戶端接 收,是以用戶端無法得知事務是否送出成功,是以用戶端會傳回一個報錯"UnknownTran sactionCommitResult ",除了這種類型的報錯需要循環以外,其他報錯無需再循環重試。
在業務環境當中,可以根據實際情況加上一個重試次數或重複時間的限制限制,避免做 無限重試。
(三)MongoDB 叢集的跨行事務(分布式事務)
從 4.2 版本開始,MongoDB 實作了叢集的跨行事務,也就是分布式事務。
叢集樣例圖如下:
分片表:
People { ssn: "hashed" }
跨行事務樣例:
這個樣例上面提到的樣例類似,不同的地方在于它的 People 表是在分片叢集裡的哈希 分片表。
它的使用方式與副本集事務完全一樣,這樣的好處在于當資料庫從副本集遷到叢集時, 業務代碼無需更改,對于開發者非常友好。
(四)MongoDB 事務包含部分 DDL
MongoDB 從 4.4 版本開始事務包含部分 DDL,事務操作包含建立表和索引,這裡有 兩種場景需要在事務中使用 DDL,第一種場景舉例如下:
如上圖所示,使用者業務一開始使用北京的 Region,并在其中部署了一個 MongoDB 庫,當在杭州擴充業務時,杭州 Region 中也需要部署一個杭州的 MongoDB 庫。
此時需要做一些庫初始化操作,例如創造一些表或者索引這些資訊,需要同步移植到杭 州庫中。因為需要保證業務庫的初始化操作符合原子性,是以需要使用事務的 DDL。
第二種場景是事務中一些 Insert 的操作,比如我們在開發測試環境當中,我們的資料 對性能要求不高,可以直接将業務代碼寫入一個初始的空庫中,此時 Insert 操作會包含自 動創表等操作,能夠也可以保證我們業務代碼能夠不報錯,但是它可能沒有索引相關資訊。
創表案例如下:
三、事務的原理
(一)MongoDB 事務的特征
All or Nothing
具有原子性。
Snapshot 隔離
事務開始時會産生 Snapshot,後續的讀寫操作不會影響 Snapshot。
Read Your Own Write
事務在新寫資料時,第一條操作為寫操作,第二個操作為讀操作,可以直接讀取事務内 尚未送出的寫操作資料,事務以外的寫操作需要等送出後才可讀取。
(二)MongoDB 事務沖突
寫操作沖突内部原理圖
如上圖所示,第一個 TXN 對文檔 1 觸發一個寫操作,它會占用文檔 1 的 Write Lock, 如果此時第二個 TXN 也進行了一個寫操作對文檔 1 做修改,那麼第二個 TNX 将無法獲得 文檔 1 的 Write Lock,事務會 Abort 全部復原。
如果此時有另外一個非事務也對文檔 1 進行寫操作,那麼它更新時也無法獲得 Write Lock,而且因為它是非事務操作,是以無法直接復原,造成阻塞。直到第一個 TXN 事務 送出,或者阻塞時間超過 MaxTimeMs,則會發生報錯。
(三)MongoDB 存儲引擎的事務能力
MongoDB 之是以能支援事務得益于存儲引擎 WiredTiger, WiredTiger 在 4.0 版 本之後使用 Timestamp 決定事務的順序性,實作了副本集的事務操作。
上圖為一個 MongoDB,由 Server 層與 WiredTiger 層組成。當進行讀操作的時候, 會産生一個 Snapshot,例如:
如上圖所示,讀操作隻能讀到之前送出的寫操作,比如 t1 與 t2,之後新寫的 t3 操作無 法讀取。
這個動作的實作主要是因為使用 WiredTiger 一個多版本并發控制 MVCC 的技術,支 持事務的沖突檢測功能。
資料和索引在 WiredTiger 的 B 樹中存儲。
下圖為 WiredTiger 存儲事務的實作圖:
WiredTiger 對事務的支援
- Update List:多版本資料,實作讀寫互斥
- Update Check
-
- 周遊 Update List,判斷是否存在沖突
- 沖突:Prepare 的修改或并發的修改
- Modify:原子操作插入到 Update List 最前面
- Read Check
-
- 周遊 Update List,找到最新可見的版本
- 沖突:Prepare 的修改
- 對 Prepare 修改造成的沖突會自動重試
(四)MongoDB 副本集的跨行事務
- 事務參數 WriteConcern: Majority 保證寫的資料不會復原;
- 事務參數 ReadConcer: Snapshot 隐含 Majority,保證讀到資料不會復原;
- MongoDB 通過這種方式,将傳統事務隔離性從單機擴充到分布式場景。
非事務的讀請求
實作原理如下:
非事務讀請求存在以下特點:
- 非事務讀請求沒有 Snapshot 隔離;
- MongoDB 在一個讀請求期間會多次 Yield,釋放 WiredTiger Snapshot;
- MongoDB 在多個讀請求同樣會使用多個 WiredTiger Snapshot。
上圖為一個非事務讀請求,在 t2 與 t3 直接觸發一次 Find,可以讀到 t1、t2。之後觸 發了一次 GetMore,同樣的周遊讀到了 t3。
讀事務的 Snapshot 隔離
- 讀事務在一個讀請求隻會使用一個 WiredTiger Snapshot;
- 讀事務在多個讀請求利用 Logical Session 保留 Context,同樣隻會使用一個 Wired Tiger Snapshot。
讀事務對存儲引擎 Cache 壓力
- Cache 壓力來自于事務 Snapshot 之後的寫請求量;
- 事務的整個生命周期會使用相同的 Snapshot;
- Update Structure 在 Snapshot 被 Evict 後才能清理。
如何避免存儲引擎 Cache 壓力
- TransactionLifetimeLimitSeconds 設為預設 60s;
- 送出 Read-Only 事務; 中止不需要的事務;
- 事務的修改的文檔大于 1000 Documents 且小于 16MB Oplog。
(五)MongoDB 叢集的跨行事務
分布式 snapshot 隔離級别:
可重複讀取多個 Shard 資料一緻的副 本集 Snapshot。
多個 Shard 資料一緻通過混合邏輯時鐘來實作,所有 Shard 節點觸發一個 Snapshot,混合邏輯時鐘可以解決分布式場景下,實體時鐘不一緻無法定序的問題。
如上圖所示,PT 是實體時鐘,然後 I 是邏輯時鐘,C 是這個邏輯時鐘之間發生的操作
數。
當兩個 Shard 之間有通信時,則産生前後的因果關系。比如看 10 秒,Shard 0 往 Shard 1 同步了一條資訊, Shard 0 的實體時鐘是 10,但 Shard 1、Shard 2、Shard 3 的實體時鐘是 0,當 Shard 0 往 Shard 1 同步一條資料後,假如 Shard 1 的實體時鐘 變成了 1,但它的邏輯時鐘是收到了時鐘 10 跟 1 的最大值,它會進行加 1,那就變成了 1, 11。
當消息在 Shard 之間直接進行多次傳遞後,所有 Shard 都會産生資料時間一緻的邏輯 時鐘。當使用邏輯時鐘,比如 I=10,c=0 時做一個 Snapshot,會發現圖中黑線的時間點, 可以保證在分布式場景下資料是一緻的。
MongoDB 叢集的跨行事務通過兩階段送出實作,原理圖如下:
- 兩階段送出:
-
- 參與者 Prepare:生成 PrepareTs
- 參與者 Commit:使用協調者收集的 max{PrepareTS} 作為 CommitTS
- 協調者:收集決策、記錄 Commit Log
- 參與者:執行事務、記錄 Prepare Log
- 故障恢複:Config 節點儲存分布式事務的狀态
-
- 協調者狀态記錄于 Config.Transaction_Coordinators
- 參與者狀态記錄于 Config.Transaction 表
(六)MongoDB 事務使用的注意事項
- 各種資料模型都能适用;
- 事務不應該是最常用的操作;
- 事務的操作中,都應該要包含 Session;
- 事務會報錯,需要增加重試邏輯;
- 不必要的事務 Snapshot 要盡快關閉;
- 如果想産生寫沖突,確定事務做了寫操作;
- 注意 DDL 操作,已有的事務操作會阻塞 DDL,DDL 會阻塞之後的事務。
四、總結
- 為什麼 MongoDB 需要事務?
- 在多對多的關系、某個事件驅動或記記錄檔時的場景下,需要進行跨表操作,同時需 要操作保持原子性,是以 MongoDB 需要事務。
- MongoDB 在副本集和叢集上跨行事務的使用方法
- 特别注意,在實際業務中根據需求,設定重試時間或重試次數。
- MongoDB 存儲引擎的事務能力。
- 副本集的跨行事務的原理,以及對 Cache 造成的壓力和避免方法。
- 叢集的跨行事務的原理。
- 事務使用的注意事項。
快速掌握MongoDB核心技術幹貨目錄
電子書下載下傳:《玩轉MongoDB從入門到實戰》 | https://developer.aliyun.com/article/780915 |
走進 MongoDB | https://developer.aliyun.com/article/781079 |
MongoDB聚合架構 | https://developer.aliyun.com/article/781095 |
複制集使用及原理介紹 | https://developer.aliyun.com/article/781137 |
分片叢集使用及原理介紹 | https://developer.aliyun.com/article/781104 |
ChangeStreams 使用及原理 | https://developer.aliyun.com/article/781107 |
事務功能使用及原理介紹 | https://developer.aliyun.com/article/781111 |
MongoDB最佳實踐一 | https://developer.aliyun.com/article/781139 |
MongoDB最佳實踐二 | https://developer.aliyun.com/article/781141 |