技術乾貨| MongoDB事務原理

MongoDB作爲領先的NoSQL,爲了支撐更多的需求場景,也在不斷完善其功能。從早期支持大吞吐量讀/寫操作的MMAPv1存儲引擎,到引入支持高併發操作的WiredTiger存儲引擎,以及對事務功能的持續演進,MongoDB不僅保留了最初的架構優勢,同時又汲取了其他數據庫的優點。

MongoDB從 3.0版本引入WiredTiger存儲引擎之後開始支持事務,MongoDB 3.6之前的版本只能支持單文檔的事務,從MongoDB 4.0版本開始支持複製集部署模式下的事務,從MongoDB 4.2版本開始支持分片集羣中的事務。

本文就主要對MongoDB事務的基本原理、事務的snapshot隔離、實現事務間併發操作的MVCC併發控制機制,以及事務日誌做一些介紹!

事務的基本原理
與關係型數據庫一樣,MongoDB事務同樣具有ACID特性,說明如下:

原子性(Automicity):一個事務要麼完全執行成功,要麼不做任何改變。

一致性(Consistency):當多個事務並行執行時,元素的屬性在每個事務中保持一致。

隔離性(Isolation):當多個事務同時執行時,互不影響。WiredTiger本身支持多種不同類型的隔離級別,如讀-未提交(read-uncommitted)、讀-已提交(read-committed)和快照(snapshot)隔離。MongoDB默認選擇的是快照隔離。

持久性(Durability):一旦提交事務,數據的更改就不會丟失。

在不同隔離級別下,一個事務的生命週期內,可能出現髒讀、不可重複讀、幻讀等現象。

下面介紹這3種現象出現的場景與含義。

  1. 髒讀現象

例如,某款手機在數據庫中的庫存還有1部,客戶A發起一個查詢手機庫存的事務,同時,客戶B發起了一個購買手機的事務(但未提交事務),此時客戶A讀到手機庫存爲0部,認爲售完了。但客戶B突然不想購買這款手機了,於是回滾了此事務,手機庫存又變爲1部,客戶A讀到的手機庫存爲0部就是一個髒讀數據,如下圖所示。

圖片圖片

  1. 不可重複讀現象

例如,某款手機在數據庫中的庫存還有1部,客戶A發起一個查詢手機庫存的事務(事務還未完成),讀到其值爲1。同時,客戶B發起了一個購買手機的事務(提交了事務),此時客戶A再次查詢手機庫存,讀到其值爲0。客戶A在同一個事務中讀到的同一條記錄的取值不一樣,這種現象就是不可重複讀,如下圖所示。

圖片圖片

  1. 幻讀現象

例如,某款手機在數據庫中的庫存還有1部,客戶A發起一個購買手機的事務(事務還未完成),讀到其值爲1。同時,管理員B發起了一個增加1部手機的事務(提交了事務),此時客戶A再次查詢手機庫存,讀到其值爲1(有新增數據)。客戶A在同一個事務中本來應該讀到的庫存值爲0,認爲手機已經售完,但發現庫存中還有1部手機,客戶A兩次讀到的數據集不一樣,這種現象就是幻讀,如下圖所示。

圖片
下面介紹與事務相關的數據結構,如下圖所示。

圖片
圖片 其中,

(1)id字段:這是事務的全局唯一標識,通過分析它與具體的操作關聯,就能夠知道一個事務包含哪些操作。

(2)snapshot_data字段:MongoDB使用的是快照隔離級別的事務,這個字段用於保存事務的快照信息,具體來說它會有snap_min和snap_max兩個屬性,通過這兩個屬性能夠計算一個事務開始時的數據範圍,每個事務開始時都會構造一個這樣的快照。

(3)commit_timestamp字段:表示事務提交的時間。

(4)durable_timestamp字段:表示事務修改的數據已持久化的時間,與具體操作中的durable_ts字段關聯。

(5)prepare_timestamp字段:表示事務開始準備的時間。

(6)WT_TXN_OP字段:包含事務的修改操作,用於事務回滾和生成事務日誌(Journal)。

(7)logrec字段:表示事務日誌的緩存,用於在內存中保存事務日誌(對於MongoDB來說Journal日誌就是事務日誌)。

事務的snapshot隔離
WiredTiger存儲引擎支持read-uncommitted、read-committed和snapshot3種事務隔離級別,MongoDB啓動時默認選擇snapshot隔離。

事務開始時,系統會創建一個快照,從已提交的事務中獲取行版本數據,如果行版本數據標識的事務尚未提交,則從更早的事務中獲取已提交的行版本數據作爲其事務開始時的值。

通過事務可以看到其他還未提交的事務修改的行版本數據,但不會看到事務id大於snap_max的事務修改的數據。

快照數據的獲取流程如下圖所示。

圖片
假設圖中的5個事務對同一條記錄進行操作,E事務開始時,生成的快照數據包含B、D兩個未完成的事務,同時獲取離它最近且完成了的C事務修改後的值作爲事務開始時的取值,即2。

如果E事務爲寫事務,對庫存值進行修改,則會進行衝突檢測,以防止對過期數據的修改,保證數據的一致性(如D事務在E事務提交之前完成,行版本已發生變化,若E事務還要進行修改,則提交時會產生衝突)。

通過一段代碼加深對快照隔離級別事務的認識:

session1 = client.start_session() //開啓一個session
session1.start_transaction() //在session內部,開啓一個事務
inventory.insert_one({'_id': 4, 'model':'switch', 'count': 200}, session= session)
doc1 = inventory.find_one({'_id': 4}, session=session1)
pprint.pprint(doc1)
doc2 = inventory.find_one({'_id': 4})
pprint.pprint(doc2)
session1.commit_transaction() //提交事務
doc3 = inventory.find_one({'_id': 4})
pprint.pprint(doc3)
session1.end_session() //結束session
任何事務都是封裝在一個session中進行的。

MVCC併發控制機制
要實現事務之間的併發操作,可以使用鎖機制或MVCC控制等。對於WiredTiger來說,使用MVCC控制來實現併發操作,相較於其他鎖機制的併發,MVCC實現的是一種樂觀併發機制。

MVCC併發控制機制如下圖所示:

圖片圖片

(1)A事務首先從表中讀取要修改的行數據,讀取的庫存值爲100,行記錄的版本號爲1。

(2)B事務也從中讀取要修改的相同行數據,讀取的庫存值爲100,行記錄的版本號爲1。

(3)A事務修改庫存值後提交,同時行記錄版本號加1,變爲2,大於A事物一開始讀取行記錄版本號1,A事務可以提交。

(4)但B事務提交時發現此時行記錄版本號已經變爲2,產生衝突,B事務提交失敗。

(5)B事務嘗試重新提交,此時再次讀取的版本號爲2,加1後版本號變爲3,不會產生衝突,正常提交B事務。

通過代碼分析事務的併發與衝突。

session1 = client.start_session() //開啓一個session1
session1.start_transaction() //在session1中開啓一個事務1
inventory.delete_one({'_id':4}, session=session1)
doc1 = inventory.find_one({'_id': 4},session=session1)
pprint.pprint(doc1) //輸出none,說明在事務中已經刪除

session2 = client.start_session() //開啓一個session2
session2.start_transaction() //在session2中開啓一個事務2
inventory.delete_one({'_id':4}, session=session2) //執行產生事務衝突

session1.abort_transaction() //終止事務1
session1.end_session() //結束session1
session2.abort_transaction() //終止事務2
session2.end_session() //結束session2

doc2 = inventory.find_one({'_id': 4}) //隱式開啓第3個session和事務
pprint.pprint(doc2) //在事務外可以找到,說明事務1被終止後回滾了

事務日誌(Journal)
Journal是一種WAL(Write Ahead Log)事務日誌,目的是實現事務提交層面的數據持久化。

Journal持久化的對象不是修改的數據,而是修改的動作,以日誌形式先保存到事務日誌緩存中,再根據相應的配置按一定的週期,將緩存中的日誌數據寫入日誌文件中。

事務日誌落盤的規則如下。

(1)按時間週期落盤。

在默認情況下,以50毫秒爲週期,將內存中的事務日誌同步到磁盤中的日誌文件。

(2)提交寫操作時強制同步落盤。

當設置寫操作的寫關注爲j:true時,強制將此寫操作的事務日誌同步到磁盤中的日誌文件。

(3)事務日誌文件的大小達到100MB。

關於作者:郭遠威

MongoDB中文社區長沙分會主席;資深大數據架構師,著有《大數據存儲MongoDB實戰指南》《MongoDB核心原理與實踐》;通信行業業務架構與數據遷移專家,先後在華爲,中興工作十餘年;曾負責實施了海外多個運營商的大數據遷移及BI等大數據系統的設計開發。

以上內容節選自《MongoDB核心原理與實踐》一書,詳情可點擊鏈接查看:
https://mp.weixin.qq.com/s/lWFvBkZ74smSjR7k7IN7wg

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章