Spring transactions and distributed transactions

1. Specific definition of transactions

Transactions provide a mechanism to incorporate all operations involved in an activity into an indivisible execution unit. All operations that make up a transaction can only be submitted if all operations can be executed normally. As long as any one of the operations fails to execute (an exception occurs) ), will cause the entire transaction to be rolled back. Simply put, transactions provide a “Do nothing, or do all (All or Nothing)” mechanism.

  • Atomicity: A transaction must be regarded as an indivisible minimum unit of work. All operations in the entire transaction must either be submitted successfully or all fail and be rolled back.
  • Consistency: The database always transitions from one consistent state to another consistent state. The integrity constraints of the database are not violated before or after the transaction begins. For example, if uniqueness is violated, the transaction must be canceled and returned to the initial state.
  • Isolation: The objects of each read-write transaction can be separated from the operation objects of other transactions, that is: the data before the transaction is submitted is invisible to other transactions, and is usually implemented with internal locking . Different isolation levels add different locks.
  • Durability: Once a transaction is committed, its modifications will be permanently saved to the database.

2. Database transactions in a concurrent environment

2.1 Problems that may arise when transactions are executed concurrently

Let’s first take a look at transaction concurrency and the problems that may arise in the database:

  • **Update lost (serious problem)**

    When there are two concurrently executed transactions updating the same row of data, it is possible that one operation will overwrite the updated data of the other operation.

  • **Dirty read (serious problem)**

    When a transaction reads data from another transaction that has not yet been committed, it reads data from the processing of the transaction, not the result data. This data may be rolled back and become invalid. If the first transaction handles invalid data, an error will occur.

  • Non-repeatable reading (generally acceptable, for example, if you pay the phone bill and check it right after payment, it may not be credited, but check again after 2 minutes and it will be credited)

    The meaning of non-repeatable read: a transaction reads the same row of data twice, but obtains different results. It is specifically divided into the following two situations:

    Virtual read: During the process of transaction 1 reading the same record twice, transaction 2 modified the record, so transaction 1 read a different record the second time.

    Phantom read: During the two queries of transaction 1, transaction 2 performed insert and delete operations on the table, so the number of results of the second query of transaction 1 changed.

What is the difference between non-repeatable read and dirty read?
Dirty read reads uncommitted data, while non-repeatable read reads committed data, but the data was changed by another transaction during the two reads.

2.2 How to solve transaction problems during concurrency (transaction isolation)

The database has four isolation levels:

  • Read uncommitted Read uncommitted

    At this level, when one transaction modifies a row of data, another transaction is not allowed to modify the row of data, but another transaction is allowed to read the row of data.

    Therefore, at this level, updates will not be lost, but dirty reads and non-repeatable reads will occur.

  • Read committed Read committed (default isolation level of oracle and sqlserver)

    At this level, uncommitted write transactions do not allow other transactions to access the row, so dirty reads will not occur; but transactions that read data allow other transactions to access the row data, so a dirty read will occur. Non-repeatable read situation.

  • Repeatable read Repeatable read (mysql’s default isolation level)

    To put it simply: when a transaction starts to read or write data, other transactions are not allowed to modify the data. At this level, read transactions prohibit write transactions, but read transactions are allowed, so the same transaction does not read different data twice (non-repeatable reading), and write transactions prohibit all other transactions. This level cannot solve the phantom reading problem.

  • Serializable Serialization

    This level requires that all transactions must be executed serially, so it can avoid all problems caused by concurrency, but the efficiency is very low.

The higher the isolation level, the more complete and consistent the data can be guaranteed, but the greater the impact on concurrency performance. For most applications, you can give priority to setting the isolation level of the database system to Read Committed. It can avoid dirty reads and has better concurrency performance. Although it will lead to concurrency problems such as non-repeatable reads and phantom reads,it should be controlled by the application programmer using pessimistic locking or optimistic locking.

3. Spring transaction propagation behavior

Transaction propagation behavior is used to describe how transactions propagate when a method modified by a certain transaction propagation behavior is nested into another method.

ServiceA {<!-- -->
         @Transactional(Propagation=XXX)
         void methodA() {<!-- -->
             //Other persistence layers operate the database
             ServiceB.methodB();
         }
}
      
ServiceB {<!-- -->
         @Transactional(Propagation=YYY)
         void methodB() {<!-- -->
            //Persistence layer operates database
         }
}

In the code, the methodA() method nestedly calls the methodB() method. The transaction propagation behavior of methodB() is determined by @Transactional( Propagation=YYY)setting decision.

Seven types of transaction propagation behaviors in Spring

Transaction Propagation Behavior Type Description
PROPAGATION_REQUIRED If there is no current transaction, create a new transaction. If there is already a transaction, add it to this transaction. This is the most common choice.
PROPAGATION_SUPPORTS Supports the current transaction. If there is no transaction currently, it will be executed in a non-transactional manner.
PROPAGATION_MANDATORY Use the current transaction. If there is no current transaction, throw an exception.
PROPAGATION_REQUIRES_NEW Create a new transaction. If a transaction currently exists, suspend the current transaction.
PROPAGATION_NOT_SUPPORTED Perform operations in a non-transactional manner. If a transaction currently exists, suspend the current transaction.
PROPAGATION_NEVER Executed in a non-transactional manner, if a transaction currently exists, an exception will be thrown.
PROPAGATION_NESTED If a transaction currently exists, it will be executed within the nested transaction. If there is no current transaction, perform similar operations to PROPAGATION_REQUIRED.

4. Spring @Transactional annotation

In the newly created Spring Boot project, spring-boot-starter or spring-boot-starter-web will generally be referenced, and these two start The dependencies already include dependencies on spring-boot-starter-jdbc or spring-boot-starter-data-jpa. When we use these two dependencies, the framework will automatically inject DataSourceTransactionManager or JpaTransactionManager respectively by default.

So we can use the @Transactional annotation to manage transactions without any additional configuration. To implement transactions for multiple database persistence layer operations within the spring framework, we only need to add the @Transactional annotation to the method or class. The @Transactional annotation can only be applied to methods with public visibility, and can be applied to interface definitions and interface methods. The methods will override the transactions declared above the class.

@Transactional
public int xxx(){<!-- -->
    //Add, delete and modify persistence layer operation one
    //Add, delete and modify persistence layer operation 2
    //…
}
?

When multiple persistence layer operations are performed on the same Service layer method, it is guaranteed that multiple persistence layer operations will either succeed or fail.

value When in There are multiple TransactionManagers in the configuration file. You can use this property to specify which transaction manager to select.
propagation The propagation behavior of the transaction, the default value is REQUIRED.
isolation The isolation degree of the transaction, the default value is DEFAULT.
timeout The timeout period of the transaction, the default value is -1. If the time limit is exceeded but the transaction is not completed, the transaction is automatically rolled back.
read-only Specifies whether the transaction is a read-only transaction, the default value is false; in order to ignore methods that do not require transactions, such as reading data , you can set read-only to true.
rollback-for is used to specify the exception type that can trigger transaction rollback. If there are multiple exception types that need to be specified, each type can separated by commas.
no-rollback- for Throws the exception type specified by no-rollback-for and does not roll back the transaction.

5. Distributed transactions

Distributed transactions are divided into two types: cross-service distributed transactions and cross-database distributed transactions.

5.1. Cross-database distributed transactions

Cross-database distributed transactions: A service layer function needs to operate two databases at the same time. The examples we have told you before are all of this kind. In fact, the general idea is: There is a “transaction manager” object that uniformly manages the submission and rollback of multiple data source transactions. The transaction manager coordinates multiple data sources for two-phase commit.

For everyone’s convenience: I will tell you about the two-stage submission in the form of a short story:

  • Background: Based on the background of the anti-drug police’s arrest of drug dealers, three drug dealers A, B, and C currently live in different addresses and are currently being arrested. The anti-drug brigade is divided into three groups. Group A, Group B, and Group C target drug dealers A, B, and C respectively. The three groups are coordinated and commanded by the “Anti-drug Brigade Captain”.
    • Three drug dealers live at different addresses, reflecting “distribution”, 3 databases
    • The “Anti-Narcotics Captain” represents the “Transaction Manager” and is responsible for the coordination and command of the arrest.
    • The three capture teams represent XAResourceManager, which is the executor of a single resource operation of the XA/JTA two-phase submission specification.
  • The requirements for arrest are: to arrest three drug dealers at the same time, A cannot be arrested first. If A fails to arrest, B and C may be alerted. Either catch them all, or catch none, so as not to alert the enemy.
    • The requirements for capturing are the same as our requirements for “distributed” transactions. Multiple database operations must either succeed or fail.
  • Steps to capture:
    • The first step: The three teams approached the addresses of drug dealers A, B, and C respectively, and then waited for the coordination and command of the “Anti-Narcotics Brigade Captain”. The “Anti-Narcotics Brigade Captain” asked Team A if they had completed preparations for the arrest. Team A replied: Preparations are complete. By analogy, the “Anti-Narcotics Brigade Captain” asked the two arrest teams B and C. These three teams were all ready and no abnormality occurred. The first phase of the work was completed. That is: The first phase of the two-phase commit: pre-commit.
    • If any team discovers anomalies, the entire action plan is immediately canceled. The three arrest teams closed in at the same time. This can be considered as a database transaction rollback.
    • Step 2: The three teams are all ready, and the “Anti-Narcotics Brigade Captain” gives the order: “Arrest.” Three arrest teams acted simultaneously and arrested three drug dealers respectively. Make sure they are all caught and no one can escape. This is like the second phase of two-phase transaction commit: overall commit.

5.2. Cross-service distributed transactions

Cross-service distributed transactions: That is to say, when I am working on a service A, I need to call multiple other services through HTTP network requests. It is possible that the first service B succeeds and the second service C fails. Our desired result is: both service B and service C succeed. This kind of distribution is difficult to solve simply by relying on the database level.

This situation is generally solved through eventual consistency. For example: send a message to service B through the MQ message queue, service B executes it, and then actually performs the persistence operation and stores the data into the database.

Send a message to service C. If service C fails to execute, the message will be stored in MQ and will be sent to service C according to a certain strategy until service C succeeds. This strategy is called “Exactly-once” and is guaranteed to succeed exactly once and only once. This ensures the ultimate consistency of the operation results.