自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

認(rèn)識MongoDB 4.0的新特性——事務(wù)(Transactions)

數(shù)據(jù)庫 其他數(shù)據(jù)庫 MongoDB
以前 MongoDB 是不支持事務(wù)的,因此開發(fā)者在需要用到事務(wù)的時(shí)候,不得不借用其他工具,在業(yè)務(wù)代碼層面去彌補(bǔ)數(shù)據(jù)庫的不足。隨著 4.0 版本的發(fā)布,MongoDB 也為我們帶來了原生的事務(wù)操作,下面就讓我們一起來認(rèn)識它,并通過簡單的例子了解如何去使用。

前言

相信使用過主流的關(guān)系型數(shù)據(jù)庫的朋友對“事務(wù)(Transactions)”不會太陌生,它可以讓我們把對多張表的多次數(shù)據(jù)庫操作整合為一次原子操作,這在高并發(fā)場景下可以保證多個(gè)數(shù)據(jù)操作之間的互不干擾;并且一旦在這些操作過程任一環(huán)節(jié)中出現(xiàn)了錯(cuò)誤,事務(wù)會中止并且讓數(shù)據(jù)回滾,這使得同時(shí)在多張表中修改數(shù)據(jù)的時(shí)候保證了數(shù)據(jù)的一致性。

以前 MongoDB 是不支持事務(wù)的,因此開發(fā)者在需要用到事務(wù)的時(shí)候,不得不借用其他工具,在業(yè)務(wù)代碼層面去彌補(bǔ)數(shù)據(jù)庫的不足。隨著 4.0 版本的發(fā)布,MongoDB 也為我們帶來了原生的事務(wù)操作,下面就讓我們一起來認(rèn)識它,并通過簡單的例子了解如何去使用。

 

 

 

[[249466]]

 

介紹

事務(wù)和副本集(Replica Sets)

副本集是 MongoDB 的一種主副節(jié)點(diǎn)架構(gòu),它使數(shù)據(jù)得到***的可用性,避免單點(diǎn)故障引起的整個(gè)服務(wù)不能訪問的情況的發(fā)生。目前 MongoDB 的多表事務(wù)操作僅支持在副本集上運(yùn)行,想要在本地環(huán)境安裝運(yùn)行副本集可以借助一個(gè)工具包——run-rs,以下的文章中有詳細(xì)的使用說明:

https://thecodebarbarian.com/...

事務(wù)和會話(Sessions)

事務(wù)和會話(Sessions)關(guān)聯(lián),一個(gè)會話同一時(shí)刻只能開啟一個(gè)事務(wù)操作,當(dāng)一個(gè)會話斷開,這個(gè)會話中的事務(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)行中報(bào)錯(cuò),catch 到的錯(cuò)誤對象中會包含一個(gè)屬性名為 errorLabels 的數(shù)組,當(dāng)這個(gè)數(shù)組中包含以下2個(gè)元素的時(shí)候,代表我們可以重新發(fā)起相應(yīng)的事務(wù)操作。

  • TransientTransactionError:出現(xiàn)在事務(wù)開啟以及隨后的數(shù)據(jù)操作階段
  • UnknownTransactionCommitResult:出現(xiàn)在提交事務(wù)階段

示例

經(jīng)過上面的鋪墊,你是不是已經(jīng)迫不及待想知道究竟應(yīng)該怎么寫代碼去完成一次完整的事務(wù)操作?下面我們就簡單寫一個(gè)例子:

場景描述: 假設(shè)一個(gè)交易系統(tǒng)中有2張表——記錄商品的名稱、庫存數(shù)量等信息的表 commodities,和記錄訂單的表 orders。當(dāng)用戶下單的時(shí)候,首先要找到 commodities 表中對應(yīng)的商品,判斷庫存數(shù)量是否滿足該筆訂單的需求,是的話則減去相應(yīng)的值,然后在 orders 表中插入一條訂單數(shù)據(jù)。在高并發(fā)場景下,可能在查詢庫存數(shù)量和減少庫存的過程中,又收到了一次新的創(chuàng)建訂單請求,這個(gè)時(shí)候可能就會出問題,因?yàn)樾碌恼埱笤诓樵儙齑娴臅r(shí)候,上一次操作還未完成減少庫存的操作,這個(gè)時(shí)候查詢到的庫存數(shù)量可能是充足的,于是開始執(zhí)行后續(xù)的操作,實(shí)際上可能上一次操作減少了庫存后,庫存的數(shù)量就已經(jīng)不足了,于是新的下單請求可能就會導(dǎo)致實(shí)際創(chuàng)建的訂單數(shù)量超過庫存數(shù)量。

以往要解決這個(gè)問題,我們可以用給商品數(shù)據(jù)“加鎖”的方式,比如基于 Redis 的各種鎖,同一時(shí)刻只允許一個(gè)訂單操作一個(gè)商品數(shù)據(jù),這種方案能解決問題,缺點(diǎn)就是代碼更復(fù)雜了,并且性能會比較低。如果用數(shù)據(jù)庫事務(wù)的方式就可以簡潔很多:

commodities 表數(shù)據(jù)(stock 為庫存):

 

  1. "_id" : ObjectId("5af0776263426f87dd69319a"), "name" : "滅霸原味手套""stock" : 5 } 
  2. "_id" : ObjectId("5af0776263426f87dd693198"), "name" : "雷神專用鐵錘""stock" : 2 } 

 

orders 表數(shù)據(jù):

 

  1. "_id" : ObjectId("5af07daa051d92f02462644c"), "commodity": ObjectId("5af0776263426f87dd69319a"), "amount": 2 } 
  2. "_id" : ObjectId("5af07daa051d92f02462644b"), "commodity": ObjectId("5af0776263426f87dd693198"), "amount": 3 } 

 

通過一次事務(wù)完成創(chuàng)建訂單操作(mongo Shell):

 

  1. // 執(zhí)行 txnFunc 并且在遇到 TransientTransactionError 的時(shí)候重試 
  2. function runTransactionWithRetry(txnFunc, session) { 
  3.   while (true) { 
  4.     try { 
  5.       txnFunc(session); // 執(zhí)行事務(wù) 
  6.       break; 
  7.     } catch (error) { 
  8.       if ( 
  9.         error.hasOwnProperty('errorLabels') && 
  10.         error.errorLabels.includes('TransientTransactionError'
  11.       ) { 
  12.         print('TransientTransactionError, retrying transaction ...'); 
  13.         continue
  14.       } else { 
  15.         throw error; 
  16.       } 
  17.     } 
  18.   } 
  19.  
  20. // 提交事務(wù)并且在遇到 UnknownTransactionCommitResult 的時(shí)候重試 
  21. function commitWithRetry(session) { 
  22.   while (true) { 
  23.     try { 
  24.       session.commitTransaction(); 
  25.       print('Transaction committed.'); 
  26.       break; 
  27.     } catch (error) { 
  28.       if ( 
  29.         error.hasOwnProperty('errorLabels') && 
  30.         error.errorLabels.includes('UnknownTransactionCommitResult'
  31.       ) { 
  32.         print('UnknownTransactionCommitResult, retrying commit operation ...'); 
  33.         continue
  34.       } else { 
  35.         print('Error during commit ...'); 
  36.         throw error; 
  37.       } 
  38.     } 
  39.   } 
  40.  
  41. // 在一次事務(wù)中完成創(chuàng)建訂單操作 
  42. function createOrder(session) { 
  43.   var commoditiesCollection = session.getDatabase('mall').commodities; 
  44.   var ordersCollection = session.getDatabase('mall').orders; 
  45.   // 假設(shè)該筆訂單中商品的數(shù)量 
  46.   var orderAmount = 3; 
  47.   // 假設(shè)商品的ID 
  48.   var commodityID = ObjectId('5af0776263426f87dd69319a'); 
  49.  
  50.   session.startTransaction({ 
  51.     readConcern: { level'snapshot' }, 
  52.     writeConcern: { w: 'majority' }, 
  53.   }); 
  54.  
  55.   try { 
  56.     var { stock } = commoditiesCollection.findOne({ _id: commodityID }); 
  57.     if (stock < orderAmount) { 
  58.       print('Stock is not enough'); 
  59.       session.abortTransaction(); 
  60.       throw new Error('Stock is not enough'); 
  61.     } 
  62.     commoditiesCollection.updateOne( 
  63.       { _id: commodityID }, 
  64.       { $inc: { stock: -orderAmount } } 
  65.     ); 
  66.     ordersCollection.insertOne({ 
  67.       commodity: commodityID, 
  68.       amount: orderAmount, 
  69.     }); 
  70.   } catch (error) { 
  71.     print('Caught exception during transaction, aborting.'); 
  72.     session.abortTransaction(); 
  73.     throw error; 
  74.   } 
  75.  
  76.   commitWithRetry(session); 
  77.  
  78. // 發(fā)起一次會話 
  79. var session = db.getMongo().startSession({ readPreference: { mode: 'primary' } }); 
  80.  
  81. try { 
  82.   runTransactionWithRetry(createOrder, session); 
  83. } catch (error) { 
  84.   // 錯(cuò)誤處理 
  85. } finally { 
  86.   session.endSession(); 

 

上面的代碼看著感覺很多,其實(shí) runTransactionWithRetry 和 commitWithRetry 這兩個(gè)函數(shù)都是可以抽離出來成為公共函數(shù)的,不需要每次操作都重復(fù)書寫。用上了事務(wù)之后,因?yàn)槭聞?wù)中的數(shù)據(jù)操作都是一次原子操作,所以我們就不需要考慮分布并發(fā)導(dǎo)致的數(shù)據(jù)一致性的問題,是不是感覺簡單了許多?

你可能注意到了,代碼中在執(zhí)行 startTransaction 的時(shí)候設(shè)置了兩個(gè)參數(shù)——readConcern 和 writeConcern,這是 MongoDB 讀寫操作的確認(rèn)級別,在這里用于在副本集中平衡數(shù)據(jù)讀寫操作的可靠性和性能,如果在這里展開就太多了,所以感興趣的朋友建議去閱讀官方文檔了解一下:

readConcern:

https://docs.mongodb.com/mast...

writeConcern:

https://docs.mongodb.com/mast... 

責(zé)任編輯:龐桂玉 來源: segmentfault
相關(guān)推薦

2009-08-10 18:16:33

ICustomQuer.NET 4.0

2009-08-19 16:51:14

C# 4.0 dyna

2009-09-04 16:28:05

ASP.NET 4.0

2018-07-05 10:55:25

數(shù)據(jù)庫MongoDB 4.0多文檔事務(wù)

2009-12-30 10:21:36

.NET 4.0

2009-05-26 09:28:22

C# 4.0dynamic動(dòng)態(tài)類型

2009-05-26 11:15:31

C# 4.0dynamicVisual Stud

2010-01-05 09:26:13

.NET 4.0

2010-02-24 14:24:35

.NET 4.0

2010-08-17 09:57:39

C#

2009-08-18 09:37:42

ASP.NET 4.0

2019-08-26 18:45:59

RedisRedis4.0數(shù)據(jù)庫

2011-01-14 10:27:18

C#.netasp.net

2024-08-15 08:00:00

MongoDB數(shù)據(jù)庫NoSQL

2009-07-06 11:00:56

.NET 4.0新特性.NET

2009-08-13 09:46:49

C#歷史C# 4.0新特性

2010-05-25 08:34:10

C# 4.0

2012-06-13 01:05:53

JavaRubyJVM

2009-01-16 10:01:57

MySQL復(fù)制特性測試

2018-03-01 14:58:55

MongoDB數(shù)據(jù)庫NoSQL
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號