跳到主要内容

事务操作

事务操作确保多个数据库操作要么全部成功,要么全部失败,保证数据的一致性。云开发支持数据库事务,并保证事务的 ACID 特性。

注意

目前数据库事务只支持在服务端运行,只有 node-sdk 支持事务。

使用场景

在大多数场景中,单文档完全可以满足需求。但在一些场景中,使用数据库事务的优势更明显:

  • 从传统关系型数据库迁移到云开发:数据模型平滑迁移,业务代码改造成本低
  • 涉及多个文档/多个集合的业务流程:保证一系列读写操作完全成功或者完全失败,防止出现中间态

支持的方法

事务流程方法

API说明
startTransaction发起事务
commit提交事务
rollback回滚事务
runTransaction自动提交事务

事务中的数据操作

API说明
get查询文档
add插入文档
delete删除文档
update更新文档
set更新文档,文档不存在时,会自动创建

基础事务

参数说明

参数类型必填说明
updateFunctionfunction✅ 是事务更新函数,接收 transaction 参数

代码示例

// 基础事务操作
const result = await db.runTransaction(async transaction => {
// 在事务中进行多个操作
const todoDoc = await transaction.collection('todos').doc('todo-id').get()

if (!todoDoc.data) {
throw new Error('待办事项不存在')
}

// 更新待办事项状态
await transaction.collection('todos').doc('todo-id').update({
data: {
completed: true,
completedAt: new Date()
}
})

// 更新用户统计
await transaction.collection('users').doc('user-id').update({
data: {
completedTodos: db.command.inc(1)
}
})

return { success: true }
})

console.log('事务执行结果:', result)

返回结果

{
result: { success: true }, // updateFunction 的返回值
errMsg: "runTransaction:ok"
}

复杂事务示例

清空购物车场景

以"清空购物车"的需求为例,展示数据库事务在复杂业务场景中的使用:

// Node.js 环境
const cloudbase = require('@cloudbase/node-sdk')
const app = cloudbase.init({})
const db = app.database()

exports.main = async (event, context) => {
const userId = 'user1'
const transaction = await db.startTransaction()

try {
const usersCollection = transaction.collection('users')
const goodsCollection = transaction.collection('goods')

// 1. 获取用户信息
const user = await usersCollection.doc(userId).get()
const { cart, balance } = user.data

// 2. 获取购物车数据和对应的商品信息
const goods = []
for (const { id } of cart) {
const good = await goodsCollection.doc(id).get()
goods.push(good.data)
}

let totalPrice = 0
for (let i = 0; i < cart.length; ++i) {
// 3. 计算购物车中的商品总价
totalPrice += cart[i].num * goods[i].price

// 4. 更新商品库存
await goodsCollection.doc(goods[i]._id).set({
inventory: goods[i].inventory - cart[i].num
})
}

// 5. 更新用户账户余额和清空购物车
await usersCollection.doc(userId).set({
balance: balance - totalPrice,
cart: []
})

await transaction.commit()

return { success: true, totalPrice }
} catch (error) {
await transaction.rollback()
throw error
}
}

事务原理

快照隔离

  • 在调用 db.startTransaction() 开始事务后,"快照"是在第一次读之后才会生成
  • 在没有调用 transaction.commit() 提交事务前,所有的读写操作都是在"快照"上进行
  • 在成功提交事务后,"快照"上的数据才会落盘,相关文档数据完成更新

锁与写冲突

  • 当事务修改文档时,会锁定相关文档,使其不受其他更改的影响,直到事务结束
  • 如果一个事务无法获取到试图修改的文档的锁,事务会终止,并出现写冲突
  • 读取文档的操作不需要与文档修改相同的锁

错误处理

推荐在进行事务操作时,使用 try-catch 来捕获异常:

try {
const transaction = await db.startTransaction()

// 涉及文档更改的事务操作
await transaction.collection('orders').add({
data: { /* 订单数据 */ }
})

// 如果发生错误,抛出异常触发回滚
if (someCondition) {
throw new Error('业务逻辑错误')
}

await transaction.collection('products').doc('product-id').update({
data: { stock: db.command.inc(-1) }
})

await transaction.commit()
} catch (error) {
console.error('事务失败,已自动回滚:', error)
// 发生写冲突时,进行异常处理
}

事务限制

操作限制

限制项说明影响
操作数量单个事务最多 100 个操作需要合理规划事务范围
执行时间事务执行时间不能超过 30 秒避免复杂计算和外部调用
文档操作仅支持 doc 方法,不支持 where 方法当前事务仅支持单文档操作
DDL 操作避免在 DDL 操作期间进行事务可能导致事务无法获得锁

注意事项

// ❌ 错误示例:在事务中进行外部调用
await db.runTransaction(async transaction => {
// 不要在事务中调用云函数或外部 API
const result = await wx.cloud.callFunction({
name: 'someFunction'
})
// 事务操作...
})

// ✅ 正确示例:先获取数据,再执行事务
const externalData = await wx.cloud.callFunction({
name: 'someFunction'
})

await db.runTransaction(async transaction => {
// 使用预先获取的数据
await transaction.collection('data').add({
data: externalData.result
})
})

最佳实践

事务设计原则

  • 避免长时间运行的事务:事务运行时间过长(超过 30s)可能会被自动终止
  • 避免执行过多操作:当一个事务中的操作过多时,可能会影响数据库性能
  • 避免在 DDL 操作时进行事务:在创建索引、删除数据库期间,事务可能无法获得锁
  • 配合 try-catch 捕获异常:尽早发现和处理写冲突、网络异常等问题

性能优化

  • 将事务拆分成更小的事务:防止长时间运行和操作过多的情况
  • 索引优化:为事务中查询的字段建立索引
  • 数据预取:在事务外预先获取需要的数据