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.
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
| API | Description |
|---|---|
| startTransaction | Initiate Transaction |
| commit | Commit Transaction |
| rollback | Rollback Transaction |
| runTransaction | Auto-commit Transaction |
Data Operations in Transactions
| API | Description |
|---|---|
| get | Query documents |
| add | Insert documents |
| delete | Delete documents |
| update | Update documents |
| set | Update documents, creating them if they do not exist |
Basic Transactions
Parameter Description
| Parameter | Type | Required | Description |
|---|---|---|---|
| updateFunction | function | ✅ Yes | Transaction 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 Item | Description | Impact |
|---|---|---|
| Operation Count | Up to 100 operations per transaction | Requires proper planning of transaction scope |
| Execution Time | Transaction execution time cannot exceed 30 seconds | Avoid complex calculations and external calls |
| Document Operations | Only supports the doc method, does not support the where method | The current transaction only supports single-document operations |
| DDL Operations | Avoid performing transactions during DDL operations | May 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
})
})