簡介
事務是資料庫中處理的邏輯組,它封裝了跨多個文件的一個或多個操作,例如讀取和/或寫入。資料庫不是單獨執行每個語句,而是能夠將這組操作作為一個有凝聚力的單元進行解釋和執行。這有助於確保資料集在許多密切相關的語句執行過程中的一致性。
在本指南中,我們將首先討論什麼是事務,何時在文件模型中使用它們,以及它們在 MongoDB 中如何概念性地工作。
如果您正在使用 MongoDB,請檢視 Prisma 的 MongoDB 聯結器!您可以使用 Prisma Client 信心十足地管理生產環境中的 MongoDB 資料庫。
要開始使用 MongoDB 和 Prisma,請檢視我們的從零開始指南或如何新增到現有專案。
什麼是事務?
事務是將多個語句組合並隔離起來,作為一個單一操作進行處理的方式。事務不是單獨執行每個傳送到伺服器的命令,而是將命令捆綁在一起,並在與其它請求分離的上下文中執行。
隔離性是事務的重要組成部分。在事務中,執行的語句只能影響事務本身的環境。從事務內部看,語句可以修改資料,結果立即可見。從外部看,在事務提交之前不會進行任何更改,此時事務中的所有操作將一次性可見。
這些特性透過提供原子性和隔離性,幫助資料庫實現 ACID 合規性。這些特性共同幫助資料庫維護一致性。此外,事務中的更改在提交到非易失性儲存之前不會被視為成功,這提供了永續性。
MongoDB 支援多文件 ACID 事務和分散式多文件 ACID 事務。本質上,文件模型允許將相關資料儲存在單個文件中。文件模型與原子文件更新相結合,在大多數用例中消除了對事務的需求。然而,在某些情況下,多文件、多集合的 MongoDB 事務是您的最佳選擇。
何時在文件模型中使用事務
文件模型本身就解決了事務所針對的許多需求。儘管如此,仍有一些用例即使在文件模型中也突出需要利用事務。
需要事務的應用程式通常是那些在不同方之間交換值的應用程式。一些例子包括“記錄系統”或“業務線”應用程式。
多文件事務的優勢專為資金流轉系統量身定製,例如銀行應用程式或支付處理、貨物所有權轉移的供應鏈或運輸系統,或電子商務。這些示例通常將跨多個流的更改打包在一起,並且需要事務提供的“全有或全無”方法來保證強一致性。
如何使用事務?
MongoDB 提供了兩種使用事務的 API。第一種是核心 API,其語法與關係型資料庫類似。第二種是回撥 API,它是 MongoDB 中使用事務的推薦方法。
兩種 API 的比較最好總結在下表中
| 核心 API | 回撥 API |
|---|---|
| 需要顯式呼叫以啟動和提交事務 | 啟動事務,執行指定操作,並提交(或在出錯時中止) |
不會自動包含針對 TransientTransactionError 和 UnknownTransactionCommitResult 的錯誤處理邏輯,而是允許整合自定義錯誤處理。 | 自動包含針對 TransientTransactionError 和 UnknownTransactionCommmitResult 的錯誤處理邏輯。 |
| 需要將顯式邏輯會話傳遞給 API 以進行特定事務。 | 需要將顯式邏輯會話傳遞給 API 以進行特定事務。 |
如果您正在尋找開始使用 MongoDB 和 Prisma 的方法,請檢視我們的從零開始指南或如何新增到現有專案。
建立事務會話
事務通常透過 API 方法之一,利用應用程式語言的相應 MongoDB 驅動程式,從外部應用程式編寫和執行。
為了演示目的,我們將透過 MongoDB shell 逐步介紹事務的建立過程。我們將使用一個示例資料庫和集合來幫助您理解事務在實踐中如何工作。
注意:如果事務會話在初始 startTransaction() 方法之後執行超過 60 秒,MongoDB 將自動中止操作。發生這種情況是因為事務通常在自動工作的應用程式中執行,而不是由人員在 shell 中發出命令。以下步驟用於概念化從開始到結束的事務會話。
首先,我們有一個名為 literature 的簡單資料庫,其中包含一個名為 authors 的集合。快速執行一個 find() 查詢,我們可以看到包含作者姓名及其作品標題的文件結構。
db.authors.find()
[{_id: ObjectId("620397dd4b871fc65c193106"),first_name: 'James',last_name: 'Joyce',title: 'Ulysses'},{_id: ObjectId("620398016ed0bb9e23785973"),first_name: 'William',last_name: 'Gibson',title: 'Neuromancer'},{_id: ObjectId("6203981d6ed0bb9e23785974"),first_name: 'George',last_name: 'Orwell',title: 'Homage to Catalonia'},{_id: ObjectId("620398516ed0bb9e23785975"),first_name: 'James',last_name: 'Baldwin',title: 'The Fire Next Time'}]
現在我們有了資料庫,我們可以展示如何在 MongoDB shell 中直接使用事務的步驟。我們還將演示在一個會話中進行的事務如何可能對外部源不可見。
首先,我們需要建立一個會話,就像您使用 API 時一樣。
var session = db.getMongo().startSession()
這個新建立的 session 變數將儲存會話物件。
下一步是透過呼叫 startTransaction() 方法來啟動事務
session.startTransaction({ "readConcern": { "level": "snapshot" },"writeConcern": { "w": "majority }})
startTransaction() 方法有兩個選項,readConcern 和 writeConcern。您可以在MongoDB 的文件中詳細瞭解這些選項。我們將 readConcern 的 level 設定為 snapshot,如果事務以 writeConcern “majority” 提交,則它會返回多數已提交資料的快照。
當您使用 w: "majority" 寫入關注和 "snapshot" 讀取關注級別進行提交時,這保證了操作具有多數已提交資料的同步快照。如果沒有特定要求,這種寫入和讀取關注的配置可以被認為是很好的預設設定。
如果成功,該方法不會返回任何內容;如果出現任何錯誤,則需要中止事務,我們將在稍後討論。
在事務會話中工作
現在您已經啟動了事務會話,您必須在上一節的 session 變數的上下文中執行語句。
在會話中用一個變量表示您的集合有助於簡化語法。您可以這樣做:
var authors = session.getDatabase('literature').getCollection('authors')
這個新建立的變數將像在事務會話之外的 shell 中工作時的 db.authors 一樣。您可以透過開啟第二個 shell 視窗,連線到您的叢集,並執行 db.authors.find() 來驗證這一點。這兩個語句都將返回相同的文件。
在會話內部,我們現在要模擬外部應用程式可能執行的操作,並向資料庫新增一條記錄。我們可以透過以下方式對 authors 集合進行操作:
authors.insertOne( {"first_name": "Virginie","last_name": "Despentes","title": "Vernon Subutex") })
MongoDB 將返回成功
{acknowledged: true,insertedId: ObjectId("6203a075c374636bc6976baa")}
如果我們現在在會話中執行 authors.find(),我們將返回之前的結果,其中包含我們新增的內容。
[{_id: ObjectId("620397dd4b871fc65c193106"),first_name: 'James',last_name: 'Joyce',best_title: 'Ulysses'},{_id: ObjectId("620398016ed0bb9e23785973"),first_name: 'William',last_name: 'Gibson',best_title: 'Neuromancer'},{_id: ObjectId("6203981d6ed0bb9e23785974"),first_name: 'George',last_name: 'Orwell',best_title: 'Homage to Catalonia'},{_id: ObjectId("620398516ed0bb9e23785975"),first_name: 'James',last_name: 'Baldwin',best_title: 'The Fire Next Time'},{_id: ObjectId("6203a075c374636bc6976baa"),first_name: 'Virginie',last_name: 'Despentes',best_title: 'Vernon Subutex'}]
由於此事務會話尚未提交,因此我們在會話外部的 MongoDB shell 中不會看到相同的結果。為了確認,我們在另一個 MongoDB shell 例項中執行 db.authors.find(),並返回原始文件。
[{_id: ObjectId("620397dd4b871fc65c193106"),first_name: 'James',last_name: 'Joyce',best_title: 'Ulysses'},{_id: ObjectId("620398016ed0bb9e23785973"),first_name: 'William',last_name: 'Gibson',best_title: 'Neuromancer'},{_id: ObjectId("6203981d6ed0bb9e23785974"),first_name: 'George',last_name: 'Orwell',best_title: 'Homage to Catalonia'},{_id: ObjectId("620398516ed0bb9e23785975"),first_name: 'James',last_name: 'Baldwin',best_title: 'The Fire Next Time'}]
這種差異表明事務目前仍處於不確定狀態。它可能成功並提交到資料庫,也可能失敗並被中止。無論哪種方式,資料庫都將最終處於一致狀態。它將包含新記錄,或者回滾到事務會話開始之前的相同狀態。
我們在會話中執行 commitTransaction() 方法,以提交事務並將資料庫帶到新的一致狀態。
session.commitTransaction()
現在,新記錄將同時存在於兩個例項中:進行事務會話的 MongoDB shell 和事務之外的 MongoDB shell。
中止事務
現在我們已經從啟動會話到提交會話,我們可以探討事務的另一種結果。
此路徑開始時相同,僅在提交點發生變化。如果我們想丟棄事務正在進行的更改,則在會話 shell 中使用 abortTransaction() 方法如下:
session.abortTransaction()
此方法取消事務會話並丟棄任何潛在更改。資料庫不會產生新的一致狀態,而是回滾更改,保持與事務會話首次啟動時相同的狀態。
結論
在本指南中,我們討論了什麼是事務以及事務在 MongoDB 中最適合的用例。我們還概念性地介紹了 MongoDB shell 中的事務會話過程,以便您瞭解外部應用程式將如何操作。
事務是關係型資料庫的基本需求,對於 MongoDB 等文件模型資料庫中的特定用例也需要事務。對於文件模型,一般的建議是,一起訪問的資料應該儲存在一起。然而,有時情況並非如此,因此瞭解事務的基礎知識很重要。
如果您正在使用 MongoDB,請檢視 Prisma 的 MongoDB 聯結器!您可以使用 Prisma Client 信心十足地管理生產環境中的 MongoDB 資料庫。
要開始使用 MongoDB 和 Prisma,請檢視我們的從零開始指南或如何新增到現有專案。
常見問題
是的,MongoDB 事務可以在 Node.js 中使用。
MongoDB 提供了一份有用的入門指南,名為“如何在 Node.js 中使用 MongoDB 事務”。
ACID 是原子性 (atomicity)、一致性 (consistency)、隔離性 (isolation) 和永續性 (durability) 的縮寫。所有這些特性都與保持資料庫處於有效狀態有關。
因為事務是同時執行的一組資料庫操作,所以在發生意外錯誤時確保資料庫有效性至關重要。符合 ACID 的事務可以防止無效的資料庫狀態。
MongoDB 事務存在於會話中。您可以使用 startSession() 建立一個會話,然後使用 session.startTransaction() 來暫存要提交的事務操作。
要有意回滾這些更改,您可以使用 session.abortTransaction() 方法丟棄會話中啟動的操作。這必須在 session.commitTransaction() 方法之前進行。
在使用 MongoDB 時,有幾個事務最佳實踐需要考慮。您需要確保最佳化以將事務執行時長保持在啟動後 60 秒內,因為 MongoDB 會自動中止任何超過此時間的事務。
事務中的運算元量不應超過 1,000 個在事務中修改的文件。此外,由於事務中會發生多個操作,因此選擇適當的讀寫關注是重要的。MongoDB 提供了一份全面的最佳實踐指南,涵蓋了這些以及更多內容。
是的,MongoDB 4.0 及更高版本支援多文件 ACID 事務。

