【MongoDB】事務

事務 Transactions

[TOC]

譯者前言:

數據模型定義對照:

MySQL MongoDB
文檔
集合
組複製 複製集

專有名詞翻譯約定:

中文 原文
讀一致性 readConcern
寫一致性 writeConcern

簡介:

4.0版本中加入

New in version 4.0

在MongoDB中,針對單個文檔的操作是原子性的。由於MongoDB允許在單個文檔中嵌入用以表示相互之間關係的子文檔和數組來替代跨文檔和集合的連接操作,這種折中的方式在很多場景下間接實現了多文檔事務的特性。

In MongoDB, an operation on a single document is atomic. Because you can use embedded documents and arrays to capture relationships between data in a single document structure instead of normalizing across multiple documents and collections, this single-document atomicity obviates the need for multi-document transactions for many practical use cases.

但這種辦法在面對多文檔同時更新或者多文檔一致性讀的時候就顯得捉襟見肘,MongoDB新版本提供了面向複製集的多文檔事務特性。其能滿足在多個操作,文檔,集合,數據庫之間的事務性,事務的特性:一個事務中的若干個操作要麼全部完成,要麼全部回滾,操作的原子性A,數據更新的一致性C。事務提交時,所有數據更改都會被永久保存D。事務提交前其數據更改不會被外部獲取到I。

However, for situations that require atomicity for updates to multiple documents or consistency between reads to multiple documents, MongoDB provides the ability to perform multi-document transactions against replica sets. Multi-document transactions can be used across multiple operations, collections, databases, and documents. Multi-document transactions provide an “all-or-nothing” proposition. When a transaction commits, all data changes made in the transaction are saved. If any operation in the transaction fails, the transaction aborts and all data changes made in the transaction are discarded without ever becoming visible. Until a transaction commits, no write operations in the transaction are visible outside the transaction.

注意:

在多數場景下,多文檔事務相對於單文檔數據變更性能損耗會更嚴重。且不要因爲支持多文檔事務就妄圖放鬆對數據結構設計的要求。無論哪種數據庫,巧妙的表設計總是會比頭鐵強行使用不必要的低級數據關聯來的高效。

一般情況下,由於減少了對多文檔事務的需求,非範式化(即內嵌子文檔和數組)的數據模型還是會發揮比較大的作用

In most cases, multi-document transaction incurs a greater performance cost over single document writes, and the availability of multi-document transaction should not be a replacement for effective schema design. For many scenarios, the denormalized data model (embedded documents and arrays) will continue to be optimal for your data and use cases. That is, for many scenarios, modeling your data appropriately will minimize the need for multi-document transactions.

事務和複製集 Transactions and Replica Sets

多文檔事務在4.0版本僅支持複製集,對分片集羣的事務性支持計劃在4.2版本中實現。

Multi-document transactions are available for replica sets only. Transactions for sharded clusters are scheduled for MongoDB 4.2 [1].

免責聲明,不翻譯了 The development, release, and timing of any features or functionality described for our products remains at our sole discretion. This information is merely intended to outline our general product direction and it should not be relied on in making a purchasing decision nor is this a commitment, promise or legal obligation to deliver any material, code, or functionality.

新特性版本向後兼容度 Feature Compatibility Version (FCV)

想使用多文檔事務的特性的話, featureCompatibilityVersion值必須設爲4.0以上。(開啓後,既存的數據不支持用4.0以下版本的mongod啓動)

The featureCompatibilityVersion (fCV) of all members of the replica set must be 4.0 or greater. To check the fCV for a member, connect to the member and run the following command:

db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } )

For more information on fCV, see setFeatureCompatibilityVersion.

存儲引擎 Storage Engines

多文檔事務特性僅支持WiredTiger存儲引擎

Multi-document transactions are only available for deployments that use WiredTiger storage engine.

Multi-document transactions are not available for deployments that use in-memory storage engine or the deprecated MMAPv1 storage engine.

事務與文檔操作 Transactions and Operations

注意事項:

For transactions:

​ 可以在任何跨庫的表上進行CURD操作

  • You can specify read/write (CRUD) operations on existing collections. The collections can be in different databases.

    config,admin,local庫中的表不支持多文檔事務

  • You cannot read/write to collections in the config, admin, or local databases.

    各庫中的system.*表不支持多文檔事務

  • You cannot write to system.* collections.

    在當前會話的事務中無法進行返回當前操作的查詢計劃

  • You cannot return the supported operation’s query plan (i.e. explain).

    在事務外創建的遊標無法在事務中進行getMore操作

  • For cursors created outside of transactions, you cannot call getMore inside a transaction.

    在事務中創建的遊標無法在事務外進行getMore操作

  • For cursors created in a transaction, you cannot call getMore outside the transaction.

在多文檔事務中無法進行諸如創建或者刪除集合,添加索引等更新數據庫元數據的操作。進一步的說,在多文檔事務中那些可能會隱式創建集合的操作也是被禁止的。

Operations that affect the database catalog, such as creating or dropping a collection or an index, are not allowed in multi-document transactions. For example, a multi-document transaction cannot include an insert operation that would result in the creation of a new collection. See Restricted Operations.

提示:

如果在創建或者刪除集合後會緊接着開啓一個會對該集合進行操作的事務,那麼創建或者刪除集合的操作需要追加寫一致性(writeConcern)擴散到多數節點的參數以確保事務可以成功獲取該集合相關的鎖。

(注,這段話可能較爲拗口。其實是從集羣的一致性來考慮,和事務的讀一致性readConcern相關)

When creating or dropping a collection immediately before starting a transaction, if the collection is accessed within the transaction, issue the create or drop operation with write concern "majority" to ensure that the transaction can acquire the required locks.

目前支持多文檔事務的命令和方法

For multi-document transactions:

Method Command Note
db.collection.aggregate() aggregate Excluding the following stages:$collStats$currentOp$indexStats$listLocalSessions$listSessions$out
db.collection.distinct() distinct
db.collection.find() find
geoSearch
db.collection.deleteMany()db.collection.deleteOne()db.collection.remove() delete
db.collection.findOneAndDelete()db.collection.findOneAndReplace()db.collection.findOneAndUpdate() findAndModify For upsert, only when run against an existing collection.
db.collection.insertMany()db.collection.insertOne()db.collection.insert() insert Only when run against an existing collection.
db.collection.save() If an insert, only when run against an existing collection.
db.collection.updateOne()db.collection.updateMany()db.collection.replaceOne()db.collection.update() update For upsert, only when run against an existing collection.
db.collection.bulkWrite()Various Bulk Operation Methods For insert operations, only when run against an existing collection.

計數操作 Count Operation

如果需要在事務中進行計數操作,需要使用 $count 操作符或者把 $group$sum結合起來

To perform a count operation within a transaction, use the $count aggregation stage or the $group (with a$sum expression) aggregation stage.

兼容4.0版本的MongoDB驅動內含一個集合層級由 $group$sum 包裝出來的 countDocuments(filter,options) 計數接口

MongoDB drivers compatible with the 4.0 features provide a collection-level API countDocuments(filter,options) as a helper method that uses the $group with a $sum expression to perform a count.

獲取參數與信息相關的操作 Informational Operations

諸如 isMaster, buildInfo, connectionStatus (及其相關的幫助命令)這些獲取參數或者信息的命令可以在事務中使用,但不能作爲事務中起手的第一個操作。

Informational commands, such as isMaster, buildInfo, connectionStatus (and their helper methods) are allowed in transactions; however, they cannot be the first operation in the transaction.

受到限制的操作 Restricted Operations

下列操作不允許在多文檔事務中使用:

The following operations are not allowed in multi-document transactions:

​ 可能會影響到數據庫元信息的操作,諸如創建或者刪除集合,增加索引。(注:其實就是DDL操作吧)。

  • Operations that affect the database catalog, such as creating or dropping a collection or an index. For example, a multi-document transaction cannot include an insert operation that would result in the creation of a new collection.

    listCollectionslistIndexes 及其相關的幫助命令都在上述禁止之列。(注:這個有點意外)

    The listCollections and listIndexes commands and their helper methods are also excluded.

    非增刪改查和非查詢參數與信息的操作,諸如 createUser, getParameter, count及其幫助命令等等。

  • Non-CRUD and non-informational operations, such as createUser, getParameter, count, etc. and their helpers.

事務與數據庫安全相關 Transactions and Security

​ 如果啓用了鑑權,欲使用多文檔事務,必須擁有操作事務的權限。

若使用了附加驗證方式,用戶名大小不能超過10K If using $external authentication users (i.e. Kerberos, LDAP, x.509 users), the usernames cannot be greater than 10k bytes.

事務與會話相關 Transactions and Sessions

事務是和會話緊密相關的,一個會話在其生命週期內最多隻能併發一個處於激活狀態的事務。

Transations are associated with a session. That is, you start a transaction for a session. At any given time, you can have at most one open transaction for a session.

重點 IMPORTANT

當使用驅動進行連接時(非mongo shell連接),必須將會話與每個事務對應起來。(注:多事務就要開啓多個連接會話類)。若在事務正在進行時會話退出,則事務會進行回滾

When using the drivers, you must pass the session to each operation in the transaction.

If a session ends and it has an open transaction, the transaction aborts.

事務與驅動的兼容 Transactions and MongoDB Drivers

以下是支持多文檔事務的各語言最低驅動版本號:

Clients require MongoDB drivers updated for MongoDB 4.0.

Java Python C# Node Ruby Perl PHPC Scala
3.8.0 3.7.0C 1.11.0 2.7 3.1.0 2.6.0 2.0.0 1.5.0 2.4.0

重點:IMPORTANT

如果想在事務中進行讀寫結合的操作,同樣必須把每個操作和會話對應起來

To associate read and write operations with a transaction, you must pass the session to each operation in the transaction. For examples, see Transactions and Retryable Writes.

事務與mongo shell工具 Transactions and the mongo Shell

下面這些mongo shell方法可以用於操作事務:

The following mongo shell methods are available for transactions:

事務和重寫 Transactions and Retryable Writes

面向高可用應用 HIGHLY AVAILABLE APPLICATIONS

無論是MongoDB還是關係型數據庫,與其連接的應用都應該設法處理事務提交過程的錯誤,設計事務的重新提做邏輯。

Regardless of the database system, whether MongoDB or relational databases, applications should take measures to handle errors during transaction commits and incorporate retry logic for transactions.

事務重做 Retry Transaction

無論 retryWrites 有沒有被設置爲true,事務中獨立的寫操作總是是無法被重做的

The individual write operations inside the transaction are not retryable, regardless of whether retryWrites is set to true.

如果操作執行時發生了錯誤,會返回一個包含名爲 errorLabels 的數組字段。如果該錯誤是暫時性的, errorLabels 會包含一個 "TransientTransactionError"元素,標誌着整個事務可以被重做。

If an operation encounters an error, the returned error may have an errorLabels array field. If the error is a transient error, the errorLabels array field contains "TransientTransactionError" as an element and the transaction as a whole can be retried.

例如,下面的下面的JAVA函數就實現了執行事務並重做那些包含"TransientTransactionError"的錯誤。

(注:官方文檔有其他幾種語言的例程,這裏選擇使用人數最多的JAVA)

For example, the following helper runs a function and retries the function if a "TransientTransactionError" is encountered:

void runTransactionWithRetry(Runnable transactional) {
    while (true) {
        try {
            transactional.run();
            break;
        } catch (MongoException e) {
            System.out.println("Transaction aborted. Caught exception during transaction.");

            if (e.hasErrorLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL)) {
                System.out.println("TransientTransactionError, aborting transaction and retrying ...");
                continue;
            } else {
                throw e;
            }
        }
    }
}

提交操作可以被重做 Retry Commit Operation

提交操作本身是可以被重做的。如果事務在提交過程中遇到錯誤,驅動會自動忽略 retryWrites的設置進行一次重試。

The commit operations are retryable write operations. If the commit operation operation encounters an error, MongoDB drivers retry the operation a single time regardless of whether retryWrites is set to true.

如果事務提交的過程中發生錯誤,MongoDB會返回一個包含 errorLabels 的數組字段。如果錯誤是暫時性的, errorLabels 字段會包含一個"UnknownTransactionCommitResult"元素,標識這個事務可以被重新提交

If the commit operation encounters an error, MongoDB returns an error with an errorLabels array field. If the error is a transient commit error, the errorLabels array field contains"UnknownTransactionCommitResult" as an element and the commit operation can be retried.

儘管MongoDB驅動提供了一次事務重新提交機制,應用方面仍應該設計一個方法去處理事務提交過程中爆出的錯誤。

In addition to the single retry behavior provided by the MongoDB drivers, applications should take measures to handle "UnknownTransactionCommitResult" errors during transaction commits.

仍以JAVA爲例:

void commitWithRetry(ClientSession clientSession) {
    while (true) {
        try {
            clientSession.commitTransaction();
            System.out.println("Transaction committed");
            break;
        } catch (MongoException e) {
            // can retry commit
            if (e.hasErrorLabel(MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)) {
                System.out.println("UnknownTransactionCommitResult, retrying commit operation ...");
                continue;
            } else {
                System.out.println("Exception during commit ...");
                throw e;
            }
        }
    }
}

事務與提交操作的重做 Retry Transaction and Commit Operation

將上文兩個邏輯結合起來,看下面的例程:(JAVA)

void runTransactionWithRetry(Runnable transactional) {
    while (true) {
        try {
            transactional.run();
            break;
        } catch (MongoException e) {
            System.out.println("Transaction aborted. Caught exception during transaction.");

            if (e.hasErrorLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL)) {
                System.out.println("TransientTransactionError, aborting transaction and retrying ...");
                continue;
            } else {
                throw e;
            }
        }
    }
}

void commitWithRetry(ClientSession clientSession) {
    while (true) {
        try {
            clientSession.commitTransaction();
            System.out.println("Transaction committed");
            break;
        } catch (MongoException e) {
            // can retry commit
            if (e.hasErrorLabel(MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)) {
                System.out.println("UnknownTransactionCommitResult, retrying commit operation ...");
                continue;
            } else {
                System.out.println("Exception during commit ...");
                throw e;
            }
        }
    }
}

void updateEmployeeInfo() {
    MongoCollection<Document> employeesCollection = client.getDatabase("hr").getCollection("employees");
    MongoCollection<Document> eventsCollection = client.getDatabase("hr").getCollection("events");

    try (ClientSession clientSession = client.startSession()) {
        clientSession.startTransaction();

        employeesCollection.updateOne(clientSession,
                Filters.eq("employee", 3),
                Updates.set("status", "Inactive"));
        eventsCollection.insertOne(clientSession,
                new Document("employee", 3).append("status", new Document("new", "Inactive").append("old", "Active")));

        commitWithRetry(clientSession);
    }
}

void updateEmployeeInfoWithRetry() {
    runTransactionWithRetry(this::updateEmployeeInfo);
}

原子性 Atomicity

多文檔事務滿足ACID的原子性

Multi-document transactions are atomic:

​ 當一個事務被提交時,該事務內部中所有的變更都會保存並且可以被其他會話的事務讀到。

  • When a transaction commits, all data changes made in the transaction are saved and visible outside the transaction. Until a transaction commits, the data changes made in the transaction are not visible outside the transaction.

    當事務回滾時,事務內部所有的變更都會被幹淨的丟棄。比如,事務中的任一操作失敗後,事務回滾,當前事務中所有的變更都會被丟棄,且不爲其他會話所知。

  • When a transaction aborts, all data changes made in the transaction are discarded without ever becoming visible. For example, if any operation in the transaction fails, the transaction aborts and all data changes made in the transaction are discarded without ever becoming visible.

讀傾向性 Read Preference

多文檔事務中的讀操作必須使用primary傾向性,即從複製集的主實例

Multi-document transactions that contain read operations must use read preference primary.

All operations in a given transaction must route to the same member.

事務參數選項 Transaction Options (Read Concern/Write Concern)

session.startTransaction( {
   readConcern: { level: <level> },
   writeConcern: { w: <value>, j: <boolean>, wtimeout: <number> }
} );

讀一致性 Read Concern

多文檔事務支持三種讀一致性等級: "snapshot", "local", and "majority",即快照,本機,多數派

Multi-document transactions support read concern "snapshot", "local", and "majority":

​ 針對本機和多數派的讀一致性等級,MongoDB在有些時候回使用更強的等級進行替換。(注:爲啥?)

  • For "local" and "majority" read concern, MongoDB may sometimes substitute a stronger read concern.

    針對多數派讀一致性等級,如果事務使用此級別進行提交,事務操作都會讀到副本集中多數派已經提交的數據。

  • For "majority" read concern, if the transaction commits with write concern “majority”, transaction operations are guaranteed to have read majority-committed data. Otherwise, the "majority" read concern provides no guarantees that read operations read majority-committed data.

    針對快照級別的讀一致性,如果。。。。。【官方文檔這兒是不是寫錯了】,如果事務使用快照級別的讀一致性進行提交,那麼可以保證事務中的操作讀到的都是事務開始時的多數派事務快照。

  • For "snapshot" read concern, if the transaction commits with write concern “majority”, the transaction operations are guaranteed to have read from a snapshot of majority committed data. Otherwise, the "snapshot" read concern provides no guarantee that read operations used a snapshot of majority-committed data.

如果針對當前事務設置讀一致性,而不是對事務中的獨立操作設置一致性,那該一致性會覆蓋事務中所有的事務。而且在事務中,事務粒度的一致性會覆蓋集合級別,數據庫級別,客戶端級別的讀一致性。

You set the read concern at the transaction level, not at the individual operation level. The operations in the transaction will use the transaction-level read concern. Any read concern set at the collection and database level is ignored inside the transaction. If the transaction-level read concern is explicitly specified, the client level read concern is also ignored inside the transaction.

可以在事務開始時對讀一致性進行設置。

You can set the transaction read concern at the transaction start.

如果缺省了事務級別的讀一致性設置,事務會繼承會話級別的讀一致性,如果會話級別也沒有設置讀一致性,那麼會繼續向上繼承客y戶端層級設置的讀一致性。

If unspecified at the transaction start, transactions use the session-level read concern or, if that is unset, the client-level read concern.

寫一致性 Write Concern

如果針對當前事務設置寫一致性,而不是對事務中的獨立操作設置一致性。那麼在提交時,事務會使用自身的一致性去提交寫操作。事務中各個獨立操作忽略寫一致性,請不要徒勞設置。(注:這段原文語義模糊,這裏認爲本段原文與上段原文寫法不同,是爲了和上段中的情況進行區分)

You set the write concern at the transaction level, not at the individual operation level. At the time of the commit, transactions use the transaction level write concern to commit the write operations. Individual operations inside the transaction ignore write concerns. Do not explicitly set the write concern for the individual write operations inside transactions.

可以在事務開始時對讀一致性進行設置。

You can set the write concern for the transaction commit at the transaction start.

​ 如果缺省了事務級別的寫一致性設置,那麼事務在提交時會繼承會話級別的寫一致性,會話級別也缺省了設置的話,會繼續向上繼承客戶端級別的寫一致性。

  • If unspecified at the transaction start, transactions use the session-level write concern for the commit or, if that is unset, the client-level write concern.

    不支持寫一致性設置爲w: 0

  • Write concern w: 0 is not supported for transactions.

    如果使用 w: 1 進行提交,那麼該事務提交時只會確認本機oK,也就是PrimaryOK。如果主節點掛掉,且該事務沒有被傳輸到當時的次級節點。那麼,恭喜你,該事務大概率會被回滾掉。會造成數據庫端和應用端數據的不一致和數據缺失。

  • If you commit using w: 1 write concern, your transaction can be rolled back if there is a failover.

    如果事務使用多數派寫一致性進行提交,且指定了快照級別的讀一致性。那麼事務操作會確保從事務開始時多數派已經提交的快照數據中進行讀取。(注:這段語義不明,官方文檔到底在表達什麼,是不是寫錯了)

  • If the transaction commits with write concern “majority” and has specified read concern "snapshot" read concern, transaction operations are guaranteed to have read from a snapshot of majority-committed data. Otherwise, the "snapshot" read concern provides no guarantees that read operations used a snapshot of majority-committed data.

    如果事務使用多數派寫一致性進行提交,且指定了多數派級別的讀一致性,那麼事務中的操作會被確保從多數派已提交的數據中進行讀取。(注:這個是不是官方文檔編輯時複製粘貼錯了,把上面讀一致性的內容放過來了)

  • If the transaction commits with write concern “majority” and has specified read concern "majority" read concern, transaction operations are guaranteed to have read majority-committed data. Otherwise, the"majority" read concern provides no guarantees that read operations read majority-committed data.

事務和鎖 Transactions and Locks

事務中的操作在獲取其所需要數據上的鎖時,默認等待超時時間爲5毫秒,超時後,當前事務會被回滾。(注:這個太短了吧,幸好可以手動設置)

By default, transactions waits 5 milliseconds to acquire locks required by the operations in the transaction. If the transaction cannot acquire its required locks within the 5 milliseconds, the transaction aborts.

提示:

如果在創建或者刪除集合後會緊接着開啓一個會對該集合進行操作的事務,那麼創建或者刪除集合的操作需要追加寫一致性(writeConcern)擴散到多數節點的參數以確保事務可以成功獲取該集合相關的鎖。

(注,這段話可能較爲拗口。其實是從集羣的一致性來考慮,和事務的讀一致性readConcern相關)

When creating or dropping a collection immediately before starting a transaction, if the collection is accessed within the transaction, issue the create or drop operation with write concern "majority" to ensure that the transaction can acquire the required locks.

可以使用 maxTransactionLockRequestTimeoutMillis 參數對鎖等待超時長度進行設置。

You can use the maxTransactionLockRequestTimeoutMillis parameter to adjust how long transactions wait to acquire locks.

也可以將 maxTransactionLockRequestTimeoutMillis 設爲-1,以禁用鎖等待超時回滾,該事務會一直等待自己所需要的鎖被其他事務或者會話釋放。

You can also use operation-specific timeout by setting maxTransactionLockRequestTimeoutMillis to -1.

Transactions release all locks upon abort or commit.

原文鏈接:MongoDB document:Transactions

翻譯:張銳志

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