全部產品
Search
文件中心

ApsaraDB for MongoDB:事務與Read/Write Concern

更新時間:Oct 16, 2024

本文將介紹關於事務以及Read/Write Concern的最佳實務,協助您更好地使用ApsaraDB for MongoDB的事務以及Read/Write Concern功能。

背景資訊

MongoDB 4.0版本支援了單機事務(複本集事務),可以在複本集內的一個或多個集合進行事務操作。MongoDB 4.2版本支援了分散式交易(分區事務),可以跨多個分區執行多個集合的不同文檔事務操作。

在MongoDB中,對於對單個文檔的操作,系統始終保證其原子性。由於MongoDB文檔結構的靈活性,業務側總是可以使用嵌入式文檔和數組結構來構造聯絡更緊密的單個文檔結構,而不是像傳統關係型資料庫那樣建立多個符合範式規則的集合并進行互動查詢或聯合更新。所以對於許多MongoDB的實際應用情境,在合理的資料建模下,單文檔原子性保證已經消除了對分散式交易的需求。

當然,一些特殊的應用情境(比如金融、會計等)依然對於分散式交易有著強烈的需求。在4.2以上版本完全支援分布式文檔以後,MongoDB也可以很好地支援這部分需求。

事務

基礎資訊

MongoDB事務的使用和互動習慣與關係型資料庫的事務基本一致,API使用方法也類似,無額外的學習成本。

以下樣本為一個完整事務,您可以看到API(startTransaction/abortTransaction/commitTransaction)以及相關聯的sessionreadConcern/writeConcern設定。

// 建立集合
db.getSiblingDB("mydb1").foo.insertOne(
    {abc: 0},
    { writeConcern: { w: "majority", wtimeout: 2000 } }
)
db.getSiblingDB("mydb2").bar.insertOne(
   {xyz: 0},
   { writeConcern: { w: "majority", wtimeout: 2000 } }
)
// 開啟一個會話
session = db.getMongo().startSession( { readPreference: { mode: "primary" } } );
coll1 = session.getDatabase("mydb1").foo;
coll2 = session.getDatabase("mydb2").bar;
// 開啟一個事務
session.startTransaction( { readConcern: { level: "local" }, writeConcern: { w: "majority" } } );
// 在事務中執行若干操作
try {
   coll1.insertOne( { abc: 1 } );
   coll2.insertOne( { xyz: 999 } );
} catch (error) {
   // 遇到問題時中止事務
   session.abortTransaction();
   throw error;
}
// 提交事務
session.commitTransaction();
session.endSession();

事務的使用說明如下:

  • 事務需要與會話(session)關聯,一個會話同一時間只能有一個未完成事務。如果會話結束,其關聯的未完成事務也會復原。

  • 一個分散式交易可以同時包含對不同庫表的不同文檔的操作。

  • 在事務執行期間,事務能夠讀取自己未提交的寫操作,但事務外部的其他動作不會讀取到事務中未提交的寫操作。

  • 在事務提交之前,不會將未提交的寫入資料複製到從節點。一旦事務被提交,寫入的資料將被複製並自動應用於所有複本集中的從節點。

  • 在修改文檔時,事務將鎖定文檔,使文檔無法被其他動作更改,直至事務完成。如果一個事務無法獲得它需要修改文檔上的鎖,可能是因為另一個事務已經持有該鎖,那麼該事務將在5毫秒後立即終止(該時間由maxTransactionLockRequestTimeoutMillis核心參數控制),並提示寫衝突(WriteConflicts)。

  • 事務有重試機制,如果遇到了暫時的可重試錯誤(比如網路臨時中斷),事務會自動重試。而且用戶端對重試操作無感知。

  • 事務有生命週期,運行超過60秒的事務將會被後台線程強制終止(該時間由transactionLifetimeLimitSeconds核心參數控制)。

使用限制

  • 分散式交易不能建立新的集合和索引。

  • 事務不能寫入capped集合。

  • 事務不能使用快照(snapshot)的Read Concern讀取capped集合(該限制存在於MongoDB 5.0及以上版本)。

  • 事務不能讀寫config/admin/local庫裡的集合。

  • 事務不能寫形如system.*的系統庫表。

  • 事務不支援explain

  • 事務無法通過getMore讀取事務外建立的遊標;事務外也無法通過getMore讀取事務內建立的遊標。

  • 事務內的第一個操作不能為killCursors/hello等。

  • 事務內無法執行非CURD的命令,包括listCollections/listIndexes/createUser/getParameter/count等。

  • 分散式交易不支援將分區上的writeConcernMajorityJournalDefault參數設定為false。

  • 分散式交易不支援帶arbiters的分區。

最佳實務

優先考慮使用單機事務而不是分散式交易

在大多數情境下,分散式交易的效能要差於單機事務或不使用事務的寫入,因為涉及到事務的操作需要有處理更多複雜情境的邏輯。在MongoDB中,非範式化的資料模型(指嵌入式文檔和數組結構)仍然是您資料建模的最佳選擇。合理的資料建模加單機事務完全可以處理絕大多數情境下應用的事務需求。

避免執行長事務

預設情況下,MongoDB將自動中止任何運行超過60秒的分散式交易。為瞭解決逾時問題,您應該將事務分解為更小的部分,以便在配置的時間限制內執行。您還需要確保已經最佳化過查詢語句,查詢語句具有適當的索引覆蓋率,以便在事務中快速地訪問資料。

避免在一個事務中修改過多文檔

在一個事務中可以讀取的文檔數量沒有硬性限制,但修改的文檔數量太多時可能會增加主從同步的壓力,從而導致從節點資料同步落後或者其他問題。推薦在一個事務中修改的文檔數量不超過1000。對於需要修改超過1000個文檔的事務,建議您將該事務分解為分批處理文檔的多個事務。

避免執行超大事務(超出16 MB)

在MongoDB 4.0中,事務用單個oplog條目表示,oplog條目大小必須在16 MB以內。MongoDB事務的更新操作僅在oplog中儲存更新的增量內容(即更改的內容),而插入操作將儲存整個文檔。因此,事務中所有語句的oplog記錄組合必須小於16 MB,如果超過這個限制,事務將被中止並完全復原。建議您將超大事務分解為較小的操作集,這些操作集可以用16 MB或更少的空間表示。

從MongoDB 4.2起,MongoDB開始支援建立多個oplog條目來儲存一個事務中的所有寫操作,相當於消除了單個超大事務16MB限制。但是依然建議您將事務的大小控制在16 MB以內,過大的事務還可能引起其他問題。

用戶端需要有合理處理交易回復(abort)的邏輯

當事務異常中止時,將向驅動程式返回一個異常並復原。您應該為應用程式添加捕獲並重試因臨時異常(如主從切換,節點故障等)而終止事務的邏輯。由於Retryable Writes機制,MongoDB驅動程式將自動重試事務的提交,但應用程式側依然需要處理那些無法被自動重試機制處理的事務異常及錯誤,包括TransactionTooLargeTransactionTooOldTransactionExceededLifetimeLimitSeconds等錯誤。

避免在事務中執行任何DDL操作

DDL操作(如createIndexdropDatabase)會被對應庫表正在啟動並執行活動事務所阻塞。當DDL操作被阻塞時,所有嘗試訪問相同庫表的事務都將無法在限定時間內獲得鎖,從而導致新事務中止。

MongoDB 4.4及以後版本最佳化了相關限制(由shouldMultiDocTxnCreateCollectionAndIndexes參數控制),您可以在分散式交易中執行createCollectioncreateIndex操作,但上述操作依舊存在以下限制:

  • 只能隱式建立。

  • 只能對當前不存在的集合執行。

  • 只能對空集合執行。

因此,建議您避免在事務中執行DDL操作。

儘可能早地主動復原不打算提交的事務以及遇到報錯的事務

所有未提交事務所涉及的修改都會駐留在WiredTiger引擎緩衝中。如果系統中同時有好幾個不打算提交的事務或遇到報錯的事務,可能會導致WiredTiger引擎的緩衝面臨很大壓力,進而引起其他問題。您應盡量控制事務操作的時間長度,儘早復原不會提交的事務來釋放資源。

若事務經常因為擷取鎖逾時而復原,可以適當調大相關逾時參數

預設情況下,事務裡的操作如果在5毫秒內擷取不到需要的鎖就會自動復原。當一個交易回復或者提交時,事務會釋放所有佔用的鎖。如果經常遇到事務因為鎖擷取逾時而復原的情況,可以適當調大maxTransactionLockRequestTimeoutMillis參數的值來規避。

如果調大參數依然不能解決此問題,請您重新審視事務裡的操作,檢查事務中是否包含了可能會長時間佔用鎖的操作(比如DDL、待最佳化的查詢),並對其進行最佳化。

盡量避免在事務內外同時修改同一文檔而導致寫衝突

如果事務進行中,事務外部的寫操作修改了一個文檔,而事務中的操作也試圖修改該文檔,事務將由於寫衝突(Write Conflicts)而復原。如果事務進行中,並且已經擷取了修改文檔需要的鎖,那麼當事務外部的寫操作試圖修改該文檔時,外部寫操作將會等待,直到事務結束。

發生寫衝突時,事務外的寫操作既不會失敗也不會返回報錯給用戶端,MongoDB內部會不斷重試並且在writeConflicts計數器上加一,直到成功為止。從用戶端的視角來看,操作並沒有異常,只是請求耗時比較久。

少量的寫衝突一般不會產生很大影響,但是如果存在大量的寫衝突,則有可能導致資料庫效能退化。您可以通過審計日誌或慢日誌確認是否存在寫衝突過多的問題。

核心缺陷風險說明

建立長時間啟動並執行大事務,或者試圖在單個事務中執行過多的操作,都會給WiredTiger儲存引擎的緩衝帶來很大壓力。因為自最早的已建立未提交事務起,WiredTiger緩衝必須能為所有後續的寫入維持相關資料和狀態。由於事務在運行時使用相同的快照,因此,在整個事務運行期間,新的寫操作會持續累積在WiredTiger緩衝中。當前運行在舊快照上的事務在提交或中止之前,緩衝中的這些寫操作都不能被逐出。而長事務引起的WiredTiger緩衝壓力超載(wt cache使用率以及dirty使用率超閾值)通常會帶來更多的問題,包括資料庫卡頓、請求延時大幅增加、CPU使用率滿等問題,甚至出現“死結”,導致業務受損。更多關於核心風險的介紹,請參見SERVER-50365SERVER-51281

ApsaraDB for MongoDB建議所有重度使用事務的業務都將MongoDB執行個體升級至5.0及以上的版本來規避相關風險和隱患。

Read Concern

基礎資訊

控制一致性和隔離等級的Read Concern包括以下幾種:

  • "local":複本集架構下讀主或從節點時的預設層級,從本地讀取,可能會讀到被復原的資料。

  • "available":分區叢集架構下讀從節點時的預設層級,可能讀到會被復原的資料。讀資料前不會進行shardVersion的檢查,因此可能讀到孤立文檔。優點是提供了最優的訪問延遲。

  • "majority":讀取的是已被大多數節點確認的資料,即不會被復原的資料。

  • "linearizable":資料一致性要求最嚴格的線性化層級,讀操作需要等待所有前序寫入都已經被大多數節點確認。效能最差,只能在主節點上使用。

  • "snapshot":基於快照讀取,同樣讀取的是已被大多數節點確認的資料,只不過可以關聯某一個特定時間點的快照,比如配合atClusterTime使用。

Read Concern的使用說明如下:

  • 無論Read Concern的層級如何,某一個mongod節點上最新的資料都不代表複本集中最近版本的資料。

  • 可以為不同的操作指定不同的Read Concern,4.4及以上版本也可以設定伺服器端預設的Read Concern,操作的Read Concern優先順序高於伺服器端設定的Read Concern。

  • 當讀取local庫時,您指定的Read Concern會被忽略,您總是能在local庫裡讀到所有本機資料。

  • 分散式交易裡僅支援3種Read Concern層級,分別為"local""majority""snapshot"

  • 因果一致性會話裡,必須使用"majority"層級的Read Concern。

最佳實務

對於分散式交易,只需設定事務的Read Concern,無需設定事務裡每個操作

不需要為事務裡的每個操作指定Read Concern。事務層級的Read Concern會覆蓋其他地方設定或預設的Read Concern。

與Write Concern一樣,Read Concern可以應用於對資料庫執行的任何查詢,而不管操作是對單個或一組文檔進行常規讀取,還是封裝在多文檔讀取事務中。

一般情境盡量使用"majority"層級的Read Concern

為了確保隔離和一致性,建議將Read Concern層級設定為"majority",只有當資料被複製到複本集中的大多數節點時,應用程式才能讀取到該資料,因此在選舉新的主節點時,資料不會復原。

Read Your Own Write的情境需要讀主並使用"local""linearizable"層級的Read Concern

為了能在寫操作完成後能儘快讀到寫入的修改,需要讀主並使用"local""linearizable"的Read Concern。當配套的Write Concern為"majority"時,也可以使用"majority"的Read Concern。

從MongoDB 3.6版本開始,您也可以使用因果一致性會話來滿足此情境。

一致性要求最強的情境需要配套maxTimeMS逾時來使用"linearizable"的Read Concern

層級為"linearizable"的Read Concern可以確保在讀取時節點仍然是複本集的主節點,並且如果隨後另一個節點被選為新的主節點,則它返回的資料不會被復原。而這個層級會對延遲產生重大影響,因此需要配套使用maxTimeMS逾時來避免大多數節點停用情況下讀操作被無限期地阻塞。

Write Concern

基礎資訊

指定Write Concern的格式如下。關於Write Concern的更多介紹,請參見Write Concern

{ w: <value>, j: <boolean>, wtimeout: <number> }
  • 控制資料持久化保證層級的Write Concern可以簡單分為以下幾種主要層級:

    • {w: 0}:表示寫(write)不確認,不確認寫操作是否完成,可能發生寫入資料丟失。image

    • {w: 1}:表示寫(write)確認,為MongoDB 5.0版本以前的預設行為。確認寫操作在記憶體中已完成,但由於還沒有持久化,依然可能發生資料丟失。image

    • {j: true}:表示日誌(journal)確認。確認寫操作已完成並刷到持久化儲存的WAL中,寫操作不會丟失。image

    • { w: "majority" }:表示大多數(majority),為MongoDB 5.0及以上版本的預設行為。等待寫操作被複製到複本集中大多數節點上後才確認,資料不會被復原。image

    • 副本確認:等待寫操作被複製到複本集中指定數量的節點上後才確認。

    • 自訂確認:可以通過settings.getLastErrorModes參數指定其他自訂的帶tag確認方式。

Write Concern的使用說明如下:

  • 您可以在任何寫操作或者事務中指定Write Concern,未顯示指定時會使用預設值。

    說明

    從MongoDB 5.0版本開始,標準3副本拓撲結構下,預設的全域Write Concern已經從之前的{w:1}調整為{w:"majority"}。這可能會導致您將資料庫版本升級至5.0及以上版本後出現效能退化問題。

  • 複本集中的隱藏(Hidden)節點、延遲(Delayed)節點、或其他優先順序為0的可投票節點均可以視為"majority"中的一員。

  • 您可以為不同的操作指定不同的Write Concern,4.4及以上版本也可以設定伺服器端預設的Write Concern,操作的Write Concern優先順序高於伺服器端設定的Write Concern。

  • 當寫入local庫時,您指定的Write Concern會被忽略。

  • 因果一致性會話裡,必須使用"majority"的Write Concern。

最佳實務

對於分散式交易而言,只需設定事務的Write Concern,無需設定事務裡每個操作

為事務內的各個寫入操作設定Write Concern會返回錯誤。

一般情境盡量使用"majority"的Write Concern

“majority”的Write Concern可以確保複本集中大部分節點已經確認寫入操作,即便此時發生節點故障或異常切換也不會產生資料丟失或者復原的風險。

對寫入效能要求高的情況酌情考慮使用{w:1}的Write Concern,並關注從節點複寫延遲

{w:1}的Write Concern通常能帶來更好的寫入效能,適合重寫入的情境。但應合理關注監控中的從節點複寫延遲,當延遲過大時可能會出現主節點異常ROLLBACK的問題。而且當複寫延遲超過了oplog的保留時間長度後,從節點將進入異常的RECOVERING狀態且無法自愈,降低執行個體可用性,應優先關注並處理。

ApsaraDB for MongoDB5.0以下版本進行批量灌資料或DTS遷移時可能會遇到上述延遲過大出現異常的問題,出現該問題時,建議您使用"majority"的Write Concern。

對於不同操作設定最適合的Write Concern

Write Concern可以在單個操作的粒度上進行設定。業務側可以根據實際操作的需要來設定不同的Write Concern。比如金融交易資料使用帶Write Concern的事務來確保原子性;核心玩家資料使用"majority"的Write Concern確保不會被復原;日誌資料使用預設或者{w:1}的Write Concern即可。

MongoDB在設計上為開發人員提供了極強的靈活性,讓不同的業務都可以根據自己的需要來設定合理的選項。

相關文檔