摘要:介紹事務(wù)和副本集副本集是的一種主副節(jié)點架構(gòu),它使數(shù)據(jù)得到最大的可用性,避免單點故障引起的整個服務(wù)不能訪問的情況的發(fā)生。在事務(wù)中執(zhí)行的數(shù)據(jù)操作是對外隔離的,也就是說事務(wù)中的操作是原子性的。中止當(dāng)前的事務(wù),并將事務(wù)中執(zhí)行過的數(shù)據(jù)修改回滾。
前言
相信使用過主流的關(guān)系型數(shù)據(jù)庫的朋友對“事務(wù)(Transactions)”不會太陌生,它可以讓我們把對多張表的多次數(shù)據(jù)庫操作整合為一次原子操作,這在高并發(fā)場景下可以保證多個數(shù)據(jù)操作之間的互不干擾;并且一旦在這些操作過程任一環(huán)節(jié)中出現(xiàn)了錯誤,事務(wù)會中止并且讓數(shù)據(jù)回滾,這使得同時在多張表中修改數(shù)據(jù)的時候保證了數(shù)據(jù)的一致性。
以前 MongoDB 是不支持事務(wù)的,因此開發(fā)者在需要用到事務(wù)的時候,不得不借用其他工具,在業(yè)務(wù)代碼層面去彌補(bǔ)數(shù)據(jù)庫的不足。隨著 4.0 版本的發(fā)布,MongoDB 也為我們帶來了原生的事務(wù)操作,下面就讓我們一起來認(rèn)識它,并通過簡單的例子了解如何去使用。
介紹 事務(wù)和副本集(Replica Sets)副本集是 MongoDB 的一種主副節(jié)點架構(gòu),它使數(shù)據(jù)得到最大的可用性,避免單點故障引起的整個服務(wù)不能訪問的情況的發(fā)生。目前 MongoDB 的多表事務(wù)操作僅支持在副本集上運(yùn)行,想要在本地環(huán)境安裝運(yùn)行副本集可以借助一個工具包——run-rs,以下的文章中有詳細(xì)的使用說明:
https://thecodebarbarian.com/...事務(wù)和會話(Sessions)
事務(wù)和會話(Sessions)關(guān)聯(lián),一個會話同一時刻只能開啟一個事務(wù)操作,當(dāng)一個會話斷開,這個會話中的事務(wù)也會結(jié)束。
事務(wù)中的函數(shù)Session.startTransaction()
在當(dāng)前會話中開始一次事務(wù),事務(wù)開啟后就可以開始進(jìn)行數(shù)據(jù)操作。在事務(wù)中執(zhí)行的數(shù)據(jù)操作是對外隔離的,也就是說事務(wù)中的操作是原子性的。
Session.commitTransaction()
提交事務(wù),將事務(wù)中對數(shù)據(jù)的修改進(jìn)行保存,然后結(jié)束當(dāng)前事務(wù),一次事務(wù)在提交之前的數(shù)據(jù)操作對外都是不可見的。
Session.abortTransaction()
中止當(dāng)前的事務(wù),并將事務(wù)中執(zhí)行過的數(shù)據(jù)修改回滾。
重試當(dāng)事務(wù)運(yùn)行中報錯,catch 到的錯誤對象中會包含一個屬性名為 errorLabels 的數(shù)組,當(dāng)這個數(shù)組中包含以下2個元素的時候,代表我們可以重新發(fā)起相應(yīng)的事務(wù)操作。
TransientTransactionError:出現(xiàn)在事務(wù)開啟以及隨后的數(shù)據(jù)操作階段
UnknownTransactionCommitResult:出現(xiàn)在提交事務(wù)階段
示例經(jīng)過上面的鋪墊,你是不是已經(jīng)迫不及待想知道究竟應(yīng)該怎么寫代碼去完成一次完整的事務(wù)操作?下面我們就簡單寫一個例子:
場景描述: 假設(shè)一個交易系統(tǒng)中有2張表——記錄商品的名稱、庫存數(shù)量等信息的表 commodities,和記錄訂單的表 orders。當(dāng)用戶下單的時候,首先要找到 commodities 表中對應(yīng)的商品,判斷庫存數(shù)量是否滿足該筆訂單的需求,是的話則減去相應(yīng)的值,然后在 orders 表中插入一條訂單數(shù)據(jù)。在高并發(fā)場景下,可能在查詢庫存數(shù)量和減少庫存的過程中,又收到了一次新的創(chuàng)建訂單請求,這個時候可能就會出問題,因為新的請求在查詢庫存的時候,上一次操作還未完成減少庫存的操作,這個時候查詢到的庫存數(shù)量可能是充足的,于是開始執(zhí)行后續(xù)的操作,實際上可能上一次操作減少了庫存后,庫存的數(shù)量就已經(jīng)不足了,于是新的下單請求可能就會導(dǎo)致實際創(chuàng)建的訂單數(shù)量超過庫存數(shù)量。
以往要解決這個問題,我們可以用給商品數(shù)據(jù)“加鎖”的方式,比如基于 Redis 的各種鎖,同一時刻只允許一個訂單操作一個商品數(shù)據(jù),這種方案能解決問題,缺點就是代碼更復(fù)雜了,并且性能會比較低。如果用數(shù)據(jù)庫事務(wù)的方式就可以簡潔很多:
commodities 表數(shù)據(jù)(stock 為庫存):
{ "_id" : ObjectId("5af0776263426f87dd69319a"), "name" : "滅霸原味手套", "stock" : 5 } { "_id" : ObjectId("5af0776263426f87dd693198"), "name" : "雷神專用鐵錘", "stock" : 2 }
orders 表數(shù)據(jù):
{ "_id" : ObjectId("5af07daa051d92f02462644c"), "commodity": ObjectId("5af0776263426f87dd69319a"), "amount": 2 } { "_id" : ObjectId("5af07daa051d92f02462644b"), "commodity": ObjectId("5af0776263426f87dd693198"), "amount": 3 }
通過一次事務(wù)完成創(chuàng)建訂單操作(mongo Shell):
// 執(zhí)行 txnFunc 并且在遇到 TransientTransactionError 的時候重試 function runTransactionWithRetry(txnFunc, session) { while (true) { try { txnFunc(session); // 執(zhí)行事務(wù) break; } catch (error) { if ( error.hasOwnProperty("errorLabels") && error.errorLabels.includes("TransientTransactionError") ) { print("TransientTransactionError, retrying transaction ..."); continue; } else { throw error; } } } } // 提交事務(wù)并且在遇到 UnknownTransactionCommitResult 的時候重試 function commitWithRetry(session) { while (true) { try { session.commitTransaction(); print("Transaction committed."); break; } catch (error) { if ( error.hasOwnProperty("errorLabels") && error.errorLabels.includes("UnknownTransactionCommitResult") ) { print("UnknownTransactionCommitResult, retrying commit operation ..."); continue; } else { print("Error during commit ..."); throw error; } } } } // 在一次事務(wù)中完成創(chuàng)建訂單操作 function createOrder(session) { var commoditiesCollection = session.getDatabase("mall").commodities; var ordersCollection = session.getDatabase("mall").orders; // 假設(shè)該筆訂單中商品的數(shù)量 var orderAmount = 3; // 假設(shè)商品的ID var commodityID = ObjectId("5af0776263426f87dd69319a"); session.startTransaction({ readConcern: { level: "snapshot" }, writeConcern: { w: "majority" }, }); try { var { stock } = commoditiesCollection.findOne({ _id: commodityID }); if (stock < orderAmount) { print("Stock is not enough"); session.abortTransaction(); throw new Error("Stock is not enough"); } commoditiesCollection.updateOne( { _id: commodityID }, { $inc: { stock: -orderAmount } } ); ordersCollection.insertOne({ commodity: commodityID, amount: orderAmount, }); } catch (error) { print("Caught exception during transaction, aborting."); session.abortTransaction(); throw error; } commitWithRetry(session); } // 發(fā)起一次會話 var session = db.getMongo().startSession({ readPreference: { mode: "primary" } }); try { runTransactionWithRetry(createOrder, session); } catch (error) { // 錯誤處理 } finally { session.endSession(); }
上面的代碼看著感覺很多,其實 runTransactionWithRetry 和 commitWithRetry 這兩個函數(shù)都是可以抽離出來成為公共函數(shù)的,不需要每次操作都重復(fù)書寫。用上了事務(wù)之后,因為事務(wù)中的數(shù)據(jù)操作都是一次原子操作,所以我們就不需要考慮分布并發(fā)導(dǎo)致的數(shù)據(jù)一致性的問題,是不是感覺簡單了許多?
你可能注意到了,代碼中在執(zhí)行 startTransaction 的時候設(shè)置了兩個參數(shù)——readConcern 和 writeConcern,這是 MongoDB 讀寫操作的確認(rèn)級別,在這里用于在副本集中平衡數(shù)據(jù)讀寫操作的可靠性和性能,如果在這里展開就太多了,所以感興趣的朋友建議去閱讀官方文檔了解一下:
readConcern:
https://docs.mongodb.com/mast...
writeConcern:
https://docs.mongodb.com/mast...
我們正在進(jìn)行限時有獎讀者調(diào)查,歡迎參加:
創(chuàng)宇前端期待聽到你的聲音
文 / Xss
本文已由作者授權(quán)發(fā)布,版權(quán)屬于創(chuàng)宇前端。歡迎注明出處轉(zhuǎn)載本文。本文鏈接:https://knownsec-fed.com/2018...
想要訂閱更多來自知道創(chuàng)宇開發(fā)一線的分享,請搜索關(guān)注我們的微信公眾號:創(chuàng)宇前端(KnownsecFED)。歡迎留言討論,我們會盡可能回復(fù)。
感謝您的閱讀。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/19414.html
摘要:官網(wǎng)中,對單文檔的操作是原子性的。因此建議使用嵌入式文檔來實現(xiàn)事務(wù)需求,而不是規(guī)范化的跨文檔設(shè)計。所以開始提供了對副本集多文檔事務(wù)的支持,注意是副本集,也就是說單是不生效的。上面創(chuàng)建的中的上添加了提供的注解,所以的事務(wù)可以和的事務(wù)統(tǒng)一管理。 官網(wǎng):mongoDB中,對單文檔的操作是原子性的。例如insertOne,updateOne等操作。因此建議使用嵌入式文檔來實現(xiàn)事務(wù)需求,而不是規(guī)...
摘要:可水平擴(kuò)展,可以添加更多服務(wù)器來擴(kuò)展您的數(shù)據(jù)庫需要管理員是否開發(fā)人員和管理員都可以使用適用場景會計師事務(wù)所和銀行,以及需要具有清晰架構(gòu)的結(jié)構(gòu)化數(shù)據(jù)的其他公司。 今天的主題是從MongoDB漫談數(shù)據(jù)庫,在日常的項目中,我們一般都是使用的mysql作為數(shù)據(jù)庫,但是一旦有問題,又常常會聽到類似要不換成MongoDB試試的聲音,因此就讓我們這些小白來隨便聊聊數(shù)據(jù)庫 什么是數(shù)據(jù)庫 我們就用最簡單...
閱讀 2524·2021-09-26 10:18
閱讀 3398·2021-09-22 10:02
閱讀 3206·2019-08-30 15:44
閱讀 3335·2019-08-30 15:44
閱讀 1841·2019-08-29 15:25
閱讀 2585·2019-08-26 14:04
閱讀 2050·2019-08-26 12:15
閱讀 2447·2019-08-26 11:43