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

創(chuàng)新互聯(lián)建站于2013年開始,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項(xiàng)目成都網(wǎng)站設(shè)計(jì)、成都做網(wǎng)站網(wǎng)站策劃,項(xiàng)目實(shí)施與項(xiàng)目整合能力。我們以讓每一個(gè)夢想脫穎而出為使命,1280元常山做網(wǎng)站,已為上家服務(wù),為常山各地企業(yè)和個(gè)人服務(wù),聯(lián)系電話:028-86922220
一、更新指令的原子操作
關(guān)系型數(shù)據(jù)庫是很難做到通過一個(gè)語句對數(shù)據(jù)強(qiáng)制一致性的需求來表示的,只能依賴事務(wù)。但是云開發(fā)數(shù)據(jù)庫由于可以反范式化設(shè)計(jì)內(nèi)嵌子文檔,以及更新指定可以對單個(gè)記錄或同一個(gè)記錄內(nèi)的子文檔進(jìn)行原子操作,所以通常情況下,云開發(fā)數(shù)據(jù)庫不必使用事務(wù)。
比如調(diào)整某個(gè)訂單項(xiàng)目的數(shù)量之后,應(yīng)該同時(shí)更新該訂單的總費(fèi)用,我們可以設(shè)計(jì)采用如下方式設(shè)計(jì)該集合,比如訂單的集合為order:
{
"_id": "2020030922100983",
"userID": "124785",
"total":117,
"orders": [{
"item":"蘋果",
"price":15,
"number":3
},{
"item":"火龍果",
"price":18,
"number":4
}]
}
客戶在下單的時(shí)候經(jīng)常會(huì)調(diào)整訂單內(nèi)某個(gè)商品比如蘋果的購買數(shù)量,而下單的總價(jià)又必須同步更新,不能購買數(shù)量減少了,但是總價(jià)不變,這兩個(gè)操作必須同時(shí)進(jìn)行,如果是使用關(guān)系型數(shù)據(jù)庫,則需要先通過兩次查詢,更新完數(shù)據(jù)之后,再存儲進(jìn)數(shù)據(jù)庫,這個(gè)很容易出現(xiàn)有的成功,有的沒有成功的情況。但是云開發(fā)的數(shù)據(jù)庫則可以借助于更新指令做到一條更新來實(shí)現(xiàn)兩個(gè)數(shù)據(jù)同時(shí)成功或失?。?/p>
db.collection('order').doc('2020030922100983')
.update({
data: {
"orders.0.number": _.inc(1),
"total":_.inc(15)
}
})這個(gè)操作只是在單個(gè)記錄里進(jìn)行,那要實(shí)現(xiàn)跨記錄要進(jìn)行原子操作呢?更新指令其實(shí)是可以做到事務(wù)仿真的,但是比較麻煩,這時(shí)就建議用事務(wù)了。
二、事務(wù)與ACID
事務(wù)就是一段數(shù)據(jù)庫語句的批處理,但是這個(gè)批處理是一個(gè)atom(原子),多個(gè)增刪改的操作是綁定在一起的,不可分割,要么都執(zhí)行,要么回滾(rollback)都不執(zhí)行。比如銀行轉(zhuǎn)賬,需要做到一個(gè)賬戶的錢匯出去了,那另外一個(gè)賬戶就一定會(huì)收到錢,不能錢匯出去了,但是錢沒有到另外一個(gè)的賬上;也就是要執(zhí)行轉(zhuǎn)賬這個(gè)事務(wù),會(huì)對A用戶的賬戶數(shù)據(jù)和B用戶的賬戶數(shù)據(jù)做增刪改的處理,這兩個(gè)處理必須一起成功一起失敗。
1.ACID
一般來說,事務(wù)是必須滿足4個(gè)條件(ACID): Atomicity(原子性)、Consistency(穩(wěn)定性)、Isolation(隔離性)、Durability(可靠性):
- 原子性:整個(gè)事務(wù)中的所有操作要么全部提交成功,要么全部失敗回滾,對于一個(gè)事務(wù)來說,不可能只執(zhí)行其中一部分操作,
- 一致性:事務(wù)的執(zhí)行不能破壞數(shù)據(jù)庫數(shù)據(jù)的完整性和一致性,一個(gè)事務(wù)在執(zhí)行前后,數(shù)據(jù)庫都必須處于一致性狀態(tài)。換句話說,事務(wù)的執(zhí)行結(jié)果必須是使數(shù)據(jù)庫從一個(gè)一致性狀態(tài)轉(zhuǎn)變到另一個(gè)一致性狀態(tài)。比如在執(zhí)行事務(wù)前,A用戶賬戶有50元,B用戶賬戶有150元;執(zhí)行B轉(zhuǎn)給A 50元事務(wù)后,兩個(gè)用戶賬戶總和還是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ù)庫能夠重新啟動(dòng),那么一定能夠?qū)⑵浠謴?fù)到事務(wù)成功結(jié)束時(shí)的狀態(tài),已提交事務(wù)的更新不會(huì)丟失。
2.云函數(shù)事務(wù)注意事項(xiàng)
(1)不支持批量操作,只支持單記錄操作
在事務(wù)中不支持批量操作(where 語句),只支持單記錄操作(collection.doc, collection.add),這可以避免大量鎖沖突、保證運(yùn)行效率,并且大多數(shù)情況下,單記錄操作足夠滿足需求,因?yàn)樵谑聞?wù)中是可以對多個(gè)單個(gè)記錄進(jìn)行操作的,也就是可以比如說在一個(gè)事務(wù)中同時(shí)對集合 A 的記錄 x 和 y 兩個(gè)記錄操作、又對集合 B 的記錄 z 操作。
(2)云數(shù)據(jù)庫采用的是快照隔離
對于兩個(gè)并發(fā)執(zhí)行的事務(wù)來說,如果涉及到操作同一條記錄的時(shí)候,可能會(huì)發(fā)生問題。因?yàn)椴l(fā)操作會(huì)帶來數(shù)據(jù)的不一致性,包括臟讀、不可重復(fù)讀、幻讀等。
- 臟讀:指當(dāng)一個(gè)事務(wù)正在訪問數(shù)據(jù),并且對數(shù)據(jù)進(jìn)行了修改,而這種修改還沒有提交到數(shù)據(jù)庫中,這時(shí),另外一個(gè)事務(wù)也訪問這個(gè)數(shù)據(jù),然后使用了這個(gè)數(shù)據(jù);
- 不可重復(fù)讀:在一個(gè)事務(wù)內(nèi)兩次讀到的數(shù)據(jù)是不一樣的,受到另一個(gè)事務(wù)修改后提交的影響,因此稱為是不可重復(fù)讀
- 幻讀:第一個(gè)事務(wù)對表進(jìn)行讀取,當(dāng)?shù)诙€(gè)事務(wù)對表進(jìn)行增加或刪除操作事務(wù)提交后,第一個(gè)事務(wù)再次讀取,會(huì)出現(xiàn)增加或減少行數(shù)的情況
云開發(fā)的數(shù)據(jù)庫系統(tǒng)的事務(wù)過程采用的是快照隔離(Snapshot isolation),可以避免并發(fā)操作帶來數(shù)據(jù)不一致的問題。
- 事務(wù)期間,讀操作返回的是對象的快照,而非實(shí)際數(shù)據(jù)
- 事務(wù)期間,寫操作會(huì):1. 改變快照,保證接下來的讀的一致性;2. 給對象加上事務(wù)鎖
- 事務(wù)鎖:如果對象上存在事務(wù)鎖,那么:1. 其它事務(wù)的寫入會(huì)直接失?。?. 普通的更新操作會(huì)被阻塞,直到事務(wù)鎖釋放或者超時(shí)
- 事務(wù)提交后,操作完畢的快照會(huì)被原子性地寫入數(shù)據(jù)庫中
三、事務(wù)操作的兩套API
云開發(fā)數(shù)據(jù)庫的事務(wù)提供兩種操作風(fēng)格的接口,一個(gè)是簡易的、帶有沖突自動(dòng)重試的 runTransaction 接口,一個(gè)是流程自定義控制的 startTransaction 接口。通過 runTransaction 回調(diào)中獲得的參數(shù) transaction 或通過 startTransaction 獲得的返回值 transaction,我們將其類比為 db 對象,只是在其上進(jìn)行的操作將在事務(wù)內(nèi)的快照完成,保證原子性。transaction 上提供的接口樹形圖一覽:
transaction
|-- collection 獲取集合引用
| |-- doc 獲取記錄引用
| | |-- get 獲取記錄內(nèi)容
| | |-- update 更新記錄內(nèi)容
| | |-- set 替換記錄內(nèi)容
| | |-- remove 刪除記錄
| |-- add 新增記錄
|-- rollback 終止事務(wù)并回滾
|-- commit 提交事務(wù)(僅在使用 startTransaction 時(shí)需調(diào)用) 1.通過 runTransaction 回調(diào)獲得 transaction
以下提供一個(gè)使用 runTransaction 接口的,兩個(gè)賬戶之間進(jìn)行轉(zhuǎn)賬的簡易示例。事務(wù)執(zhí)行函數(shù)由開發(fā)者傳入,函數(shù)接收一個(gè)參數(shù) transaction,其上提供 collection 方法和 rollback 方法。collection 方法用于取數(shù)據(jù)庫集合記錄引用進(jìn)行操作,rollback 方法用于在不想繼續(xù)執(zhí)行事務(wù)時(shí)終止并回滾事務(wù)。
const cloud = require('wx-server-sdk')
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV
})
const _ = db.command
exports.main = async (event) => {
try {
const result = await db.runTransaction(async transaction => {
const aaaRes = await transaction.collection('account').doc('aaa').get()
const bbbRes = await transaction.collection('account').doc('bbb').get()
if (aaaRes.data && bbbRes.data) {
const updateAAARes = await transaction.collection('account').doc('aaa').update({
data: {
amount: _.inc(-10)
}
})
const updateBBBRes = await transaction.collection('account').doc('bbb').update({
data: {
amount: _.inc(10)
}
})
console.log(`transaction succeeded`, result)
return {
aaaAccount: aaaRes.data.amount - 10,
}
} else {
await transaction.rollback(-100)
}
})
return {
success: true,
aaaAccount: result.aaaAccount,
}
} catch (e) {
console.error(`事務(wù)報(bào)錯(cuò)`, e)
return {
success: false,
error: e
}
}
}
事務(wù)執(zhí)行函數(shù)必須為 async 異步函數(shù)或返回 Promise 的函數(shù),當(dāng)事務(wù)執(zhí)行函數(shù)返回時(shí),SDK 會(huì)認(rèn)為用戶邏輯已完成,自動(dòng)提交(commit)事務(wù),因此務(wù)必確保用戶事務(wù)邏輯完成后才在 async 異步函數(shù)中返回或 resolve Promise。
2.通過 startTransaction 獲得transaction
- db.startTransaction(),開啟一個(gè)新的事務(wù),之后即可進(jìn)行 CRUD 操作;
- db.startTransaction().transaction.commit(),提交事務(wù)保存數(shù)據(jù),在提交之前事務(wù)中的變更的數(shù)據(jù)對外是不可見的;
- db.startTransaction().rollback(),事務(wù)終止并回滾事務(wù),例如,一部分?jǐn)?shù)據(jù)更新失敗,對已修改過的數(shù)據(jù)也進(jìn)行回滾。
const cloud = require('wx-server-sdk')
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV
})
const db = cloud.database({
throwOnNotFound: false,
})
const _ = db.command
exports.main = async (event) => {
try {
const transaction = await db.startTransaction()
const aaaRes = await transaction.collection('account').doc('aaa').get()
const bbbRes = await transaction.collection('account').doc('bbb').get()
if (aaaRes.data && bbbRes.data) {
const updateAAARes = await transaction.collection('account').doc('aaa').update({
data: {
amount: _.inc(-10)
}
})
const updateBBBRes = await transaction.collection('account').doc('bbb').update({
data: {
amount: _.inc(10)
}
})
await transaction.commit()
return {
success: true,
aaaAccount: aaaRes.data.amount - 10,
}
} else {
await transaction.rollback()
return {
success: false,
error: `rollback`,
rollbackCode: -100,
}
}
} catch (e) {
console.error(`事務(wù)報(bào)錯(cuò)`, e)
}
}
也就是說對于多用戶同時(shí)操作(主要是寫)數(shù)據(jù)庫的并發(fā)處理問題,我們不僅可以使用原子更新,還可以使用事務(wù)。其中原子更新主要用戶操作單個(gè)記錄內(nèi)的字段或單個(gè)記錄里內(nèi)嵌的數(shù)組對象里的字段,而事務(wù)則主要是用于跨記錄和跨集合的處理。
分享名稱:創(chuàng)新互聯(lián)小程序云教程:云開發(fā)原子操作和事務(wù)
URL分享:http://fisionsoft.com.cn/article/coshegg.html


咨詢
建站咨詢
