Use [inventory] to understand cloud development database transactions

Posted May 27, 20206 min read

Use [inventory]to understand cloud development database transactions

What is a transaction?

A database transaction is a logical unit in the execution process of a database management system. It consists of a limited sequence of database operations. These operations are either all executed or not executed at all, and are an inseparable unit of work. A transaction consists of all database operations performed between the beginning and the end of a transaction.

Characteristics of transactions

ACID refers to the abbreviation of the four main characteristics of the correct execution of database transactions. A transaction must have these four basic characteristics, otherwise the accuracy of the data cannot be guaranteed during the transaction process

1:atomicity(Atomicity)

It means that all operations in a transaction form an atomic packet, and all succeed or fail.

If an attribute is updated in the database, but an exception occurs halfway through the execution, this may cause the data after the operation to be different from our expected data, so atomicity requires you to either execute all of this method successfully, or all failure

2:Consistency

Refers to the database must be in a consistent state before and after the execution of a transaction.

It is stipulated in the atomicity that the operations in the method are executed or not executed, but it does not say that all operations should be executed together, so the execution of the operations is in order, so if we query the database during half of the execution, then we will get Updated property in the middle? Consistency stipulates that there are only two states before and after submission, the state before submission and the state after submission

3:Isolation

Refers to the database's ability to allow multiple concurrent transactions to read, write and modify its data at the same time. Isolation can prevent data inconsistency due to cross-execution when multiple transactions are executed concurrently.

Multiple transactions may operate on the same database resource. Different transactions may cause several problems if they are not isolated.

  1. Transaction A reads the unmodified data modified by transaction B, and transaction B rolls back the data modification operation, resulting in the dirty data obtained by transaction A
  2. Transaction A reads the data first, and transaction B modifies the data. When transaction B reads the row of data again, it will cause inconsistencies between the two reads.
  3. When transaction A reads the data and transaction B operates on it, when transaction A reads the data again, it will cause inconsistency between the two queries

The cloud development database currently uses snapshot isolation, which will be described below

4:Durability

After the transaction is completed, the modification of the data is permanent, even if the system fails, it will not be lost

If there is no persistence feature, once the database is abnormal, the data will be lost

Once a persistent transaction is submitted, the data in the database must be permanently preserved. Even if the server system crashes or the server is down, as long as the database is restarted, it will be able to restore it to the state after the transaction successfully completed.

Cloud development database transaction

Introduction

The cloud development database itself provides data structure design of atomic operation symbols(such as inc, mul, addToSet) and nested records, such as the atomic operation across multiple records or across multiple sets, you can use cloud database transaction capabilities.

isolation

The snapshot isolation level(snapshot) used in the cloud development database transaction process. During the transaction, the read operation returns a snapshot of the object, not the actual data. When the write operation is performed during the transaction:

  1. Change the snapshot to ensure the consistency of the next read;
  2. Add transaction lock to the object

Transaction Lock

The effect of transaction locks on data objects on data writing:

  1. The writing of other transactions will directly fail;
  2. Ordinary update operations will be blocked until the transaction lock is released or the timeout transaction is submitted, and the completed snapshot will be written to the database atomically

Single record operation

Cloud development database transactions do not support batch operations, only single record operations such as(collection.doc, collection.add). Single record operations can avoid a large number of lock conflicts and ensure operating efficiency, and in most cases single record operations are sufficient to meet demand , Because multiple single records can be operated on in a transaction, that is, both records x and y of set A and records z of set B can be operated in a transaction at the same time. A small example to demonstrate.

Transaction API

The cloud development database transaction provides two operation style interfaces, one is a simple runTransaction interface with conflict automatic retry, and the other is a startTransaction interface for custom process control.

Use small example

Assume the following scenario:

A warehouse has 1,000 boxes of medical masks, hospital A needs 800 boxes, and hospital B needs 300 boxes and submits the application. The management mode of the warehouse is to first receive the submitted application and confirm it after completing the inventory commodity confirmation.

Pseudo code executes from top to bottom without transactions

const cloud = require('wx-server-sdk')
cloud.init({
  env:cloud.DYNAMIC_CURRENT_ENV
})
const db = cloud.database()
const _ = db.command


exports.main = async(event, context) => {
//hospital
 await db.collection('resource'). doc('A' || 'B')
    .update({
      data:{
        resource:_.inc(-800 || -300)
      },
    })

//warehouse
 await db.collection('store'). doc('store')
    .update({
      data:{
        resource:_.inc(+800 || +300),
      },
    })
}
//determine whether the requirements are met
if('warehouse inventory'> 'received quantity') {
await db.collection('store'). doc('store')
    .update({
      data:{
        count:_inc(-800 || -300),
      },
    })
 } eles {

    'Business logic of going back'

 }

}

According to the above code execution results:

  1. The A/B hospital submitted an application for masks;
  2. The warehouse received the application submitted by the B hospital;
  3. Determine whether it meets the quantity requirements

When it is found that the warehouse inventory does not meet the hospital's collection requirements until 3 o'clock, it is necessary to return the submitted application to the hospital and handle some of the logic of the return.

In this case, a large amount of processing operations and high complexity are required.In the case of high concurrent execution, some specific operations will not be completed.For example:

  1. The hospital submitted the application, but the warehouse did not receive it;
  2. The hospital submitted the application, the warehouse received the application and did not execute the distribution, nor returned to the hospital;

Pseudo-code execution from top to bottom in case of transactions

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 result = await db.runTransaction(async transaction => {

      const resource = await transaction.collection('resource'). doc('A' || 'B'). get()
      const store = await transaction.collection('store'). doc('store'). get()

        const updateResource = await transaction.collection('resource'). doc('A' || 'B'). update({
          data:{
           resource:_.inc(-800 || -300)
          }
        })

        const updateStoreResource = await transaction.collection('store'). doc('store'). update({
          data:{
           resource:_.inc(+800 || +300),
          }
        })

        if(store.data.count> 800 || 300) {
         const updateStoreCount = await transaction.collection('store'). doc('store'). update({
              data:{
                 count:_inc(-800 || -300),
                  }
                })
         //will be returned as the result of runTransaction resolve
          return {
              resourceAccount:resource.data.count + 800 || 300,
            }

        } else {

        //Will go out as a result of runTransaction reject
        await transaction.rollback('Failed to receive')

        }

    })

    return {
      success:true,
      resourceAccount:result.resourceAccount,
    }
  } catch(e) {
    console.error(`transaction error`, e)

    return {
      success:false,
      error:e
    }
  }
}

According to the above code execution results:

  1. First read the snapshot of records of A/B hospital and warehouse;
  2. The hospital submits an application to reduce the corresponding number;
  3. The warehouse receives the application submitted by the hospital;
  4. Determine whether the quantity in the warehouse meets the quantity received this time;

When it is found that the warehouse inventory does not meet the hospital's collection requirements until 4th time, the transaction will restore all the changed records to the data when reading the record snapshot, that is to say, these execution steps are either successful or not. Both fail, the data is rolled back, and no excessive rollback logic is required`

Unused transactions VS Used transactions

Unused transactions

  1. Due to the large amount of operations and high complexity, there will be data inconsistencies when high concurrency is added;
  2. The fallback logic is complicated;

Use transaction

  1. The transaction consists of a limited sequence of database operations. These operations are either performed or not performed at all, ensuring data consistency;
  2. After executing the transaction, a snapshot of the data object is retained, and any problems in the execution can be directly rolled back;

to sum up

In the cloud development database, if it is only related to the modification of a single record, it is entirely possible to use atomic operation symbols such as inc, mul, addToSet), which involves the simultaneous modification of multiple collections and multiple records and the need to ensure consistency. That transaction function will be the best choice.