May 22, 2021 Mini Program Cloud Development Advanced
Update instructions such as inc, mul, addToSet allow you to perform atomic operations on a record in a cloud database and sub-documents within a record (combined with an anti-paradigm design), but you need to use the transaction capabilities of the cloud database when you want to operate atomically across multiple records or collections.
A relationship database is difficult to represent by the need for data enforcement consistency through a statement and can only rely on transactions. However, cloud development databases typically do not have to use transactions because they can reverse paradigms to design inline sub-documents and updates that specify that atomic operations can be performed on a single record or sub-documents within the same record.
For example, after adjusting the quantity of an order item, the total cost of the order should be updated at the same time, and we can design the collection in the following way, such as the order's collection as an order:
{
"_id": "2020030922100983",
"userID": "124785",
"total":117,
"orders": [{
"item":"苹果",
"price":15,
"number":3
},{
"item":"火龙果",
"price":18,
"number":4
}]
}
Customers often adjust the order of an item such as Apple's purchase quantity, and the total price of the order must be updated synchronously, can not buy the quantity reduced, but the total price is unchanged, these two operations must be carried out at the same time, if the use of the relationship database, you need to first through two queries, update the data, and then stored in the database, this is easy to have some success, some did not succeed. However, a database developed by the cloud can be updated with the help of an update instruction to achieve two data successes or failures at the same time:
db.collection('order').doc('2020030922100983')
.update({
data: {
"orders.0.number": _.inc(1),
"total":_.inc(15)
}
})
This is done only in a single record, so what about the atomic operation to implement across records? Update instructions can actually do transaction simulation, but more troublesome, then it is recommended to use transactions.
A transaction is a batch of database statements, but this batch is an atom (atomic), and multiple additions and deletions are bound together, indivisible, executed either or rollback. For example, bank transfer, need to do one account money remitted out, that another account will certainly receive money, can not transfer money out, but the money did not go to another account;
In general, transactions must meet four conditions (ACID): Atomicity (Atomic), Consistency (Stability), Isolation (Isolation), Durability (Reliability):
(1) Bulk operations are not supported, only single-record operations are supported
Bulk operations (where statements) are not supported in transactions, only single-record operations (collection.doc, collection.add), which avoids a large number of lock conflicts, guarantees operational efficiency, and in most cases, single-record operations are sufficient because multiple individual records can be operated in a transaction, i.e. both record x and y of collection B in a transaction.
(2) The cloud database uses snapshot isolation
For two transactions that are executed in a synth, problems can occur when it comes to operating the same record. Because of the inconsistencies in the data between the operations, including dirty reads, non-repeatable reads, phantom reads, and so on.
The transactional process of a database system developed by the cloud uses snapshot isolation to avoid data insanity created by the same operation.
Transactions in the cloud development database provide interfaces in two operating styles, a simple runTransaction interface with conflicting autotry, and a startTransaction interface with process custom control. U sing the parameter transaction obtained in the runTransaction callback or the return value transaction obtained through startTransaction, we liken it to a db object, but the operations performed on it are done in a snapshot within the transaction, ensuring atomicity. A list of interface tree diagrams provided on the transaction:
transaction
|-- collection 获取集合引用
| |-- doc 获取记录引用
| | |-- get 获取记录内容
| | |-- update 更新记录内容
| | |-- set 替换记录内容
| | |-- remove 删除记录
| |-- add 新增记录
|-- rollback 终止事务并回滚
|-- commit 提交事务(仅在使用 startTransaction 时需调用)
The following provides a simple example of a transfer between two accounts using the runTransaction interface. T he transaction execution function is passed in by the developer, and the function receives an argument transaction that provides the collection method and the rollback method. The collection method is used to take a database collection record reference for operations, and the rollback method is used to terminate and roll back transactions if they do not want to continue.
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(`事务报错`, e)
return {
success: false,
error: e
}
}
}
The transaction execution function must be an async asynchronous function or a function that returns Promise, and when the transaction execution function returns, the SDK considers that the user logic is complete and automatically commits the transaction, so it is important to ensure that the user transaction logic is completed before returning or resolveing in the async asynchronous function.
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(`事务报错`, e)
}
}
That is, for multi-user simultaneous operations (mainly write) databases to handle the problem of the same time, we can not only use atomic updates, but also use transactions. Where the atomic update primary user manipulates fields within a single record or fields in an array object embedded in a single record, transactions are primarily used for processing across records and collections.