# 事务
仅支持云函数端使用,
wx-server-sdk
最低版本要求1.7.0
# 介绍
如果原子操作符(如 inc
、mul
、addToSet
)和嵌套记录的数据结构设计无法满足需求,需要更高可自定义的事务操作,如跨多个记录或跨多集合的原子操作时(比如两个账户之间转账),可以使用云数据库事务能力。
# 快照隔离
事务过程采用的是快照隔离,在快照隔离中会保证:
- 事务期间,读操作返回的是对象的快照,而非实际数据
- 事务期间,写操作会:1. 改变快照,保证接下来的读的一致性;2. 给对象加上事务锁
- 事务锁:如果对象上存在事务锁,那么:1. 其它事务的写入会直接失败;2. 普通的更新操作会被阻塞,直到事务锁释放或者超时
- 事务提交后,操作完毕的快照会被原子性地写入数据库中
# 单记录操作
在事务中不支持批量操作(where 语句),只支持单记录操作(collection.doc, collection.add),这可以避免大量锁冲突、保证运行效率,并且大多数情况下单记录操作足够满足需求,因为在事务中是可以对多个单个记录进行操作的,也就是可以比如说在一个事务中同时对集合 A 的记录 x
和 y
两个记录操作、又对集合 B 的记录 z
操作。
# API
事务提供两种操作风格的接口,一个是简易的、带有冲突自动重试的 runTransaction
接口,一个是流程自定义控制的 startTransaction
接口。详细定义可参见 API 文档。
通过 runTransaction
回调中获得的参数 transaction
或通过 startTransaction
获得的返回值 transaction
,我们将其类比为 db
对象,只是在其上进行的操作将在事务内的快照完成,保证原子性。transaction
上提供的接口树形图一览:
transaction
|-- collection 获取集合引用
| |-- doc 获取记录引用
| | |-- get 获取记录内容
| | |-- update 更新记录内容
| | |-- set 替换记录内容
| | |-- remove 删除记录
| |-- add 新增记录
|-- rollback 终止事务并回滚
|-- commit 提交事务(仅在使用 startTransaction 时需调用)
以下提供一个使用 runTransaction
接口的,两个账户之间进行转账的简易示例。注意使用 runTransaction
时,传入的回调即事务执行函数必须为 async 异步函数或返回 Promise 的函数,当事务执行函数返回时,SDK 会认为用户逻辑已完成,自动提交(commit)事务,因此务必确保用户事务逻辑完成后才在 async 异步函数中返回或 resolve Promise。同时在使用事务时建议初始化 db
时指定 throwOnNotFound
为 false
,指定 false 后可使得 doc.get 在找不到记录时不抛出异常。
const cloud = require('wx-server-sdk')
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV
})
const db = cloud.database({
// 该参数从 wx-server-sdk 1.7.0 开始支持,默认为 true,指定 false 后可使得 doc.get 在找不到记录时不抛出异常
throwOnNotFound: false,
})
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)
}
})
// 会作为 runTransaction resolve 的结果返回
return {
aaaAccount: aaaRes.data.amount - 10,
}
} else {
// 会作为 runTransaction reject 的结果出去
await transaction.rollback(-100)
}
})
console.log(`transaction succeeded`, result)
return {
success: true,
aaaAccount: result.aaaAccount,
}
} catch (e) {
console.error(`transaction error`, e)
return {
success: false,
error: e
}
}
}
# 隔离等级
目前数据库是快照隔离,没有串行化隔离,无法避免写偏(write skew)的情况。
# FAQ
部分环境的数据库可能会无法使用,可以在社区发帖,我们会有专人处理。当报错信息是 [object Object](callback err is not instance of Error)
或 [BadRequest] Not Found
时,请到社区反馈。