Skip to main content

Transaction Operations

Transactional operations ensure that multiple database operations either all succeed or all fail, guaranteeing data consistency. CloudBase supports database transactions and ensures the ACID properties of transactions.

Note

Currently, database transactions are only supported for server-side execution, and only the node-sdk supports transactions.

Use Cases

In most scenarios, single-document operations can fully meet requirements. However, in some cases, database transactions offer more distinct advantages compared to single-document operations:

  • Migrating from Traditional Relational Databases to CloudBase: Smooth data model migration with low business code transformation costs.
  • Business Processes Involving Multiple Documents/Collections: Ensure that a series of read/write operations either fully succeed or completely fail, preventing intermediate states.

Supported Methods

Transaction Process Methods

APIDescription
startTransactionInitiate Transaction
commitCommit Transaction
rollbackRollback Transaction
runTransactionAuto-commit Transaction

Data Operations in Transactions

APIDescription
getQuery documents
addInsert documents
deleteDelete documents
updateUpdate documents
setUpdate documents, creating them if they do not exist

Basic Transactions

Parameter Description

ParameterTypeRequiredDescription
updateFunctionfunction✅ YesTransaction update function that receives a transaction parameter

Code Example

const cloudbase = require('@cloudbase/node-sdk')

// Initialize the database.
const app = cloudbase.init({
env: 'your-env-id'
})
const db = app.database()

// Basic Transaction Operations
const result = await db.runTransaction(async transaction => {
// Perform multiple operations within a transaction
const todoDoc = await transaction.collection('todos').doc('todo-id').get()

if (!todoDoc.data) {
throw new Error('Todo item does not exist')
}

// Update todo item status
await transaction.collection('todos').doc('todo-id').update({
data: {
completed: true,
completedAt: new Date()
}
})

// Update user statistics
await transaction.collection('users').doc('user-id').update({
data: {
completedTodos: db.command.inc(1)
}
})

return { success: true }
})

console.log('Transaction execution result:', result)

Result

{
result: result: { success: true }, // Return value of updateFunction
errMsg: "runTransaction:ok"
}

Complex Transaction Example

Clear Shopping Cart Scenario

Using the "Clear Shopping Cart" requirement as an example, this demonstrates the use of database transactions in complex business scenarios:

// Node.js environment
const cloudbase = require('@cloudbase/node-sdk')

// Initialize the database.
const app = cloudbase.init({
env: 'your-env-id'
})
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. Obtain user information.
const user = await usersCollection.doc(userId).get()
const { cart, balance } = user.data

// 2. Obtain shopping cart data and corresponding product information.
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. Calculate the total price of items in the shopping cart.
totalPrice += cart[i].num * goods[i].price

// 4. Update product inventory.
await goodsCollection.doc(goods[i]._id).set({
inventory: goods[i].inventory - cart[i].num
})
}

// 5. Update user account balance and clear the shopping cart.
await usersCollection.doc(userId).set({
balance: balance - totalPrice,
cart: []
})

await transaction.commit()

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

Transaction Mechanism

Snapshot Isolation

  • When db.startTransaction() is called to initiate a transaction, the "snapshot" is generated only after the first read operation
  • Before calling transaction.commit() to commit the transaction, all read and write operations are performed on the "snapshot"
  • After the transaction is successfully committed, the data in the "snapshot" is persisted to disk, and the relevant document data is updated

Lock and Write Conflict

  • When a transaction modifies a document, it locks the relevant documents to prevent them from being affected by other modifications until the transaction ends
  • If a transaction fails to obtain the lock on the documents it is attempting to modify, the transaction will abort with a write conflict
  • Reading documents does not require the same lock as modifying documents

Error Handling

It is recommended to use try-catch to capture exceptions during transaction operations:

try {
const transaction = await db.startTransaction()

// Transaction operations involving document modifications
await transaction.collection('orders').add({
data: data: { /* order data */ }
})

// If an error occurs, throw an exception to trigger a rollback
if (someCondition) {
throw new Error('Business logic error')
}

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

await transaction.commit()
} catch (error) {
console.error('Transaction failed, automatically rolled back:', error)
// When a write conflict occurs, perform exception handling
}

Transaction Limitations

Operation Restrictions

Limitation ItemDescriptionImpact
Operation CountUp to 100 operations per transactionRequires proper planning of transaction scope
Execution TimeTransaction execution time cannot exceed 30 secondsAvoid complex calculations and external calls
Document OperationsOnly supports the doc method, does not support the where methodThe current transaction only supports single-document operations
DDL OperationsAvoid performing transactions during DDL operationsMay cause transactions to fail to acquire locks

Notes

// ❌ Error example: Making external calls within a transaction
await db.runTransaction(async transaction => {
// Do not call cloud functions or external APIs within a transaction
const result = await wx.cloud.callFunction({
name: 'someFunction'
})
// Transaction operations...
})

// ✅ Correct example: Obtain data first, then execute the transaction
const externalData = await wx.cloud.callFunction({
name: 'someFunction'
})

await db.runTransaction(async transaction => {
// Use pre-obtained data
await transaction.collection('data').add({
data: externalData.result
})
})