云開(kāi)發(fā) 原子操作和事務(wù)

2020-07-22 15:32 更新

使用更新指令(如 inc、mul、addToSet)可以對(duì)云數(shù)據(jù)庫(kù)的一條記錄和記錄內(nèi)的子文檔(結(jié)合反范式化設(shè)計(jì))進(jìn)行原子操作,但是如果要跨多個(gè)記錄或跨多個(gè)集合的原子操作時(shí),就需要使用云數(shù)據(jù)庫(kù)的事務(wù)能力。

一、更新指令的原子操作

關(guān)系型數(shù)據(jù)庫(kù)是很難做到通過(guò)一個(gè)語(yǔ)句對(duì)數(shù)據(jù)強(qiáng)制一致性的需求來(lái)表示的,只能依賴(lài)事務(wù)。但是云開(kāi)發(fā)數(shù)據(jù)庫(kù)由于可以反范式化設(shè)計(jì)內(nèi)嵌子文檔,以及更新指定可以對(duì)單個(gè)記錄或同一個(gè)記錄內(nèi)的子文檔進(jìn)行原子操作,所以通常情況下,云開(kāi)發(fā)數(shù)據(jù)庫(kù)不必使用事務(wù)。

比如調(diào)整某個(gè)訂單項(xiàng)目的數(shù)量之后,應(yīng)該同時(shí)更新該訂單的總費(fèi)用,我們可以設(shè)計(jì)采用如下方式設(shè)計(jì)該集合,比如訂單的集合為order:

  1. {
  2. "_id": "2020030922100983",
  3. "userID": "124785",
  4. "total":117,
  5. "orders": [{
  6. "item":"蘋(píng)果",
  7. "price":15,
  8. "number":3
  9. },{
  10. "item":"火龍果",
  11. "price":18,
  12. "number":4
  13. }]
  14. }

客戶(hù)在下單的時(shí)候經(jīng)常會(huì)調(diào)整訂單內(nèi)某個(gè)商品比如蘋(píng)果的購(gòu)買(mǎi)數(shù)量,而下單的總價(jià)又必須同步更新,不能購(gòu)買(mǎi)數(shù)量減少了,但是總價(jià)不變,這兩個(gè)操作必須同時(shí)進(jìn)行,如果是使用關(guān)系型數(shù)據(jù)庫(kù),則需要先通過(guò)兩次查詢(xún),更新完數(shù)據(jù)之后,再存儲(chǔ)進(jìn)數(shù)據(jù)庫(kù),這個(gè)很容易出現(xiàn)有的成功,有的沒(méi)有成功的情況。但是云開(kāi)發(fā)的數(shù)據(jù)庫(kù)則可以借助于更新指令做到一條更新來(lái)實(shí)現(xiàn)兩個(gè)數(shù)據(jù)同時(shí)成功或失?。?/p>

  1. db.collection('order').doc('2020030922100983')
  2. .update({
  3. data: {
  4. "orders.0.number": _.inc(1),
  5. "total":_.inc(15)
  6. }
  7. })

這個(gè)操作只是在單個(gè)記錄里進(jìn)行,那要實(shí)現(xiàn)跨記錄要進(jìn)行原子操作呢?更新指令其實(shí)是可以做到事務(wù)仿真的,但是比較麻煩,這時(shí)就建議用事務(wù)了。

二、事務(wù)與ACID

事務(wù)就是一段數(shù)據(jù)庫(kù)語(yǔ)句的批處理,但是這個(gè)批處理是一個(gè)atom(原子),多個(gè)增刪改的操作是綁定在一起的,不可分割,要么都執(zhí)行,要么回滾(rollback)都不執(zhí)行。比如銀行轉(zhuǎn)賬,需要做到一個(gè)賬戶(hù)的錢(qián)匯出去了,那另外一個(gè)賬戶(hù)就一定會(huì)收到錢(qián),不能錢(qián)匯出去了,但是錢(qián)沒(méi)有到另外一個(gè)的賬上;也就是要執(zhí)行轉(zhuǎn)賬這個(gè)事務(wù),會(huì)對(duì)A用戶(hù)的賬戶(hù)數(shù)據(jù)和B用戶(hù)的賬戶(hù)數(shù)據(jù)做增刪改的處理,這兩個(gè)處理必須一起成功一起失敗。

1.ACID

一般來(lái)說(shuō),事務(wù)是必須滿足4個(gè)條件(ACID): Atomicity(原子性)、Consistency(穩(wěn)定性)、Isolation(隔離性)、Durability(可靠性):

  • 原子性:整個(gè)事務(wù)中的所有操作要么全部提交成功,要么全部失敗回滾,對(duì)于一個(gè)事務(wù)來(lái)說(shuō),不可能只執(zhí)行其中一部分操作,

  • 一致性:事務(wù)的執(zhí)行不能破壞數(shù)據(jù)庫(kù)數(shù)據(jù)的完整性和一致性,一個(gè)事務(wù)在執(zhí)行前后,數(shù)據(jù)庫(kù)都必須處于一致性狀態(tài)。換句話說(shuō),事務(wù)的執(zhí)行結(jié)果必須是使數(shù)據(jù)庫(kù)從一個(gè)一致性狀態(tài)轉(zhuǎn)變到另一個(gè)一致性狀態(tài)。比如在執(zhí)行事務(wù)前,A用戶(hù)賬戶(hù)有50元,B用戶(hù)賬戶(hù)有150元;執(zhí)行B轉(zhuǎn)給A 50元事務(wù)后,兩個(gè)用戶(hù)賬戶(hù)總和還是200元。

  • 隔離性:事務(wù)的隔離性是指在并發(fā)環(huán)境中,當(dāng)不同的事務(wù)同時(shí)操縱相同的數(shù)據(jù)時(shí),每個(gè)事務(wù)都有各自的完整數(shù)據(jù)空間事務(wù)之間,互不干擾。比如在線銀行,同時(shí)轉(zhuǎn)賬的人雖然很多,但是不會(huì)出現(xiàn)影響A與B之間的轉(zhuǎn)賬;

  • 可靠性:即使發(fā)生系統(tǒng)崩潰或機(jī)器宕機(jī)等故障,只要數(shù)據(jù)庫(kù)能夠重新啟動(dòng),那么一定能夠?qū)⑵浠謴?fù)到事務(wù)成功結(jié)束時(shí)的狀態(tài),已提交事務(wù)的更新不會(huì)丟失。

2.云函數(shù)事務(wù)注意事項(xiàng)

(1)不支持批量操作,只支持單記錄操作

在事務(wù)中不支持批量操作(where 語(yǔ)句),只支持單記錄操作(collection.doc, collection.add),這可以避免大量鎖沖突、保證運(yùn)行效率,并且大多數(shù)情況下,單記錄操作足夠滿足需求,因?yàn)樵谑聞?wù)中是可以對(duì)多個(gè)單個(gè)記錄進(jìn)行操作的,也就是可以比如說(shuō)在一個(gè)事務(wù)中同時(shí)對(duì)集合 A 的記錄 x 和 y 兩個(gè)記錄操作、又對(duì)集合 B 的記錄 z 操作。

(2)云數(shù)據(jù)庫(kù)采用的是快照隔離

對(duì)于兩個(gè)并發(fā)執(zhí)行的事務(wù)來(lái)說(shuō),如果涉及到操作同一條記錄的時(shí)候,可能會(huì)發(fā)生問(wèn)題。因?yàn)椴l(fā)操作會(huì)帶來(lái)數(shù)據(jù)的不一致性,包括臟讀、不可重復(fù)讀、幻讀等。

  • 臟讀:指當(dāng)一個(gè)事務(wù)正在訪問(wèn)數(shù)據(jù),并且對(duì)數(shù)據(jù)進(jìn)行了修改,而這種修改還沒(méi)有提交到數(shù)據(jù)庫(kù)中,這時(shí),另外一個(gè)事務(wù)也訪問(wèn)這個(gè)數(shù)據(jù),然后使用了這個(gè)數(shù)據(jù);

  • 不可重復(fù)讀:在一個(gè)事務(wù)內(nèi)兩次讀到的數(shù)據(jù)是不一樣的,受到另一個(gè)事務(wù)修改后提交的影響,因此稱(chēng)為是不可重復(fù)讀

  • 幻讀:第一個(gè)事務(wù)對(duì)表進(jìn)行讀取,當(dāng)?shù)诙€(gè)事務(wù)對(duì)表進(jìn)行增加或刪除操作事務(wù)提交后,第一個(gè)事務(wù)再次讀取,會(huì)出現(xiàn)增加或減少行數(shù)的情況

云開(kāi)發(fā)的數(shù)據(jù)庫(kù)系統(tǒng)的事務(wù)過(guò)程采用的是快照隔離(Snapshot isolation),可以避免并發(fā)操作帶來(lái)數(shù)據(jù)不一致的問(wèn)題。

  • 事務(wù)期間,讀操作返回的是對(duì)象的快照,而非實(shí)際數(shù)據(jù)

  • 事務(wù)期間,寫(xiě)操作會(huì):1. 改變快照,保證接下來(lái)的讀的一致性;2. 給對(duì)象加上事務(wù)鎖

  • 事務(wù)鎖:如果對(duì)象上存在事務(wù)鎖,那么:1. 其它事務(wù)的寫(xiě)入會(huì)直接失?。?. 普通的更新操作會(huì)被阻塞,直到事務(wù)鎖釋放或者超時(shí)

  • 事務(wù)提交后,操作完畢的快照會(huì)被原子性地寫(xiě)入數(shù)據(jù)庫(kù)中

三、事務(wù)操作的兩套API

云開(kāi)發(fā)數(shù)據(jù)庫(kù)的事務(wù)提供兩種操作風(fēng)格的接口,一個(gè)是簡(jiǎn)易的、帶有沖突自動(dòng)重試的 runTransaction 接口,一個(gè)是流程自定義控制的 startTransaction 接口。通過(guò) runTransaction 回調(diào)中獲得的參數(shù) transaction 或通過(guò) startTransaction 獲得的返回值 transaction,我們將其類(lèi)比為 db 對(duì)象,只是在其上進(jìn)行的操作將在事務(wù)內(nèi)的快照完成,保證原子性。transaction 上提供的接口樹(shù)形圖一覽:

  1. transaction
  2. |-- collection 獲取集合引用
  3. | |-- doc 獲取記錄引用
  4. | | |-- get 獲取記錄內(nèi)容
  5. | | |-- update 更新記錄內(nèi)容
  6. | | |-- set 替換記錄內(nèi)容
  7. | | |-- remove 刪除記錄
  8. | |-- add 新增記錄
  9. |-- rollback 終止事務(wù)并回滾
  10. |-- commit 提交事務(wù)(僅在使用 startTransaction 時(shí)需調(diào)用)

1.通過(guò) runTransaction 回調(diào)獲得 transaction

以下提供一個(gè)使用 runTransaction 接口的,兩個(gè)賬戶(hù)之間進(jìn)行轉(zhuǎn)賬的簡(jiǎn)易示例。事務(wù)執(zhí)行函數(shù)由開(kāi)發(fā)者傳入,函數(shù)接收一個(gè)參數(shù) transaction,其上提供 collection 方法和 rollback 方法。collection 方法用于取數(shù)據(jù)庫(kù)集合記錄引用進(jìn)行操作,rollback 方法用于在不想繼續(xù)執(zhí)行事務(wù)時(shí)終止并回滾事務(wù)。

  1. const cloud = require('wx-server-sdk')
  2. cloud.init({
  3. env: cloud.DYNAMIC_CURRENT_ENV
  4. })
  5. const _ = db.command
  6. exports.main = async (event) => {
  7. try {
  8. const result = await db.runTransaction(async transaction => {
  9. const aaaRes = await transaction.collection('account').doc('aaa').get()
  10. const bbbRes = await transaction.collection('account').doc('bbb').get()
  11. if (aaaRes.data && bbbRes.data) {
  12. const updateAAARes = await transaction.collection('account').doc('aaa').update({
  13. data: {
  14. amount: _.inc(-10)
  15. }
  16. })
  17. const updateBBBRes = await transaction.collection('account').doc('bbb').update({
  18. data: {
  19. amount: _.inc(10)
  20. }
  21. })
  22. console.log(`transaction succeeded`, result)
  23. return {
  24. aaaAccount: aaaRes.data.amount - 10,
  25. }
  26. } else {
  27. await transaction.rollback(-100)
  28. }
  29. })
  30. return {
  31. success: true,
  32. aaaAccount: result.aaaAccount,
  33. }
  34. } catch (e) {
  35. console.error(`事務(wù)報(bào)錯(cuò)`, e)
  36. return {
  37. success: false,
  38. error: e
  39. }
  40. }
  41. }

事務(wù)執(zhí)行函數(shù)必須為 async 異步函數(shù)或返回 Promise 的函數(shù),當(dāng)事務(wù)執(zhí)行函數(shù)返回時(shí),SDK 會(huì)認(rèn)為用戶(hù)邏輯已完成,自動(dòng)提交(commit)事務(wù),因此務(wù)必確保用戶(hù)事務(wù)邏輯完成后才在 async 異步函數(shù)中返回或 resolve Promise。

2.通過(guò) startTransaction 獲得transaction

  • db.startTransaction(),開(kāi)啟一個(gè)新的事務(wù),之后即可進(jìn)行 CRUD 操作;

  • db.startTransaction().transaction.commit(),提交事務(wù)保存數(shù)據(jù),在提交之前事務(wù)中的變更的數(shù)據(jù)對(duì)外是不可見(jiàn)的;

  • db.startTransaction().rollback(),事務(wù)終止并回滾事務(wù),例如,一部分?jǐn)?shù)據(jù)更新失敗,對(duì)已修改過(guò)的數(shù)據(jù)也進(jìn)行回滾。

  1. const cloud = require('wx-server-sdk')
  2. cloud.init({
  3. env: cloud.DYNAMIC_CURRENT_ENV
  4. })
  5. const db = cloud.database({
  6. throwOnNotFound: false,
  7. })
  8. const _ = db.command
  9. exports.main = async (event) => {
  10. try {
  11. const transaction = await db.startTransaction()
  12. const aaaRes = await transaction.collection('account').doc('aaa').get()
  13. const bbbRes = await transaction.collection('account').doc('bbb').get()
  14. if (aaaRes.data && bbbRes.data) {
  15. const updateAAARes = await transaction.collection('account').doc('aaa').update({
  16. data: {
  17. amount: _.inc(-10)
  18. }
  19. })
  20. const updateBBBRes = await transaction.collection('account').doc('bbb').update({
  21. data: {
  22. amount: _.inc(10)
  23. }
  24. })
  25. await transaction.commit()
  26. return {
  27. success: true,
  28. aaaAccount: aaaRes.data.amount - 10,
  29. }
  30. } else {
  31. await transaction.rollback()
  32. return {
  33. success: false,
  34. error: `rollback`,
  35. rollbackCode: -100,
  36. }
  37. }
  38. } catch (e) {
  39. console.error(`事務(wù)報(bào)錯(cuò)`, e)
  40. }
  41. }

也就是說(shuō)對(duì)于多用戶(hù)同時(shí)操作(主要是寫(xiě))數(shù)據(jù)庫(kù)的并發(fā)處理問(wèn)題,我們不僅可以使用原子更新,還可以使用事務(wù)。其中原子更新主要用戶(hù)操作單個(gè)記錄內(nèi)的字段或單個(gè)記錄里內(nèi)嵌的數(shù)組對(duì)象里的字段,而事務(wù)則主要是用于跨記錄和跨集合的處理。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)