The concept of a transaction: We call one or more database operations that require atomicity, isolation, consistency, and persistence a transaction.

For example, in the case of a bank transfer transaction, it is necessary to ensure that both the transferor’s account and the transferee’s account are either successful or fail together.

Four characteristics of database transactions

Database transactions have the following four characteristics, referred to as ACID

  • Atomic: The operations contained in a transaction are considered to be an overall business unit, in which all operations either succeed or fail, with no partial success or partial failure scenarios.

  • Consistency: All data must be in a consistent state upon completion of a transaction. All changes in the database are transaction-based to ensure data integrity.

  • Isolation: In actual Internet application scenarios, multiple application threads may access the same data at the same time. In this way, the same data in the database will not be accessed in different transactions, resulting in lost updates. In order to suppress the occurrence of lost updates, the database defines the concept of isolation level. By choosing different transaction isolation levels, the occurrence of lost updates can be suppressed to different degrees (because in Internet applications often need to face high concurrency scenarios, so isolation is the focus of programmers to master).

  • “Durability” : After a transaction ends, all data is fixed in one place, such as on disk, accessible by applications even if they reboot without power.

2 The isolation level of the transaction

To suppress lost updates, the database standard proposes four isolation levels that suppress lost updates to varying degrees. They are uncommitted reads, read committed, repeatability, and serialization. The isolation level of transactions is one of the most frequently asked questions of job candidates. I was asked three out of five interviews for senior Java development.

2.1 Uncommitted read

Read uncommited is the lowest isolation level that allows one transaction to read data that has not been committed by another transaction. Uncommitted reads are a dangerous isolation level, so we generally don’t use them much in actual development. However, its advantage lies in its high concurrency capability, suitable for those scenarios that have no requirements for data consistency and pursue high concurrency. The biggest disadvantage is that dirty reads occur, which is when a transaction reads data that has been modified by another uncommitted transaction.

Let’s look at the scenarios where dirty reads can occur:



Table 1 Dirty read fields generated by uncommitted reads

Uncommitted reads are generally hazardous isolation levels that are not often used in our practice. To overcome the problem of dirty reads, data isolation levels also provide read and write committed levels

2.2 Read Submitted

Read Committed means that a transaction can only read data committed by another transaction, but not uncommitted data. The scenario in Table 1 becomes the scenario in Table 2 after limiting read and write submissions

Table 2 Read Commit Overcome dirty read scenario

At time T3, transaction 2 cannot read inventory 1 that has not been committed by transaction 1 because of the read committed isolation level, so the result is still 1 after subtracting inventory. It then commits the transaction and inventory becomes 1 at time T4. At time T5, transaction 1 is rolled back and the final result inventory is 1, which is a correct result. However, reading committed can also create an unrepeatable read scenario.

Table 3 Unrepeatable read scenarios

When the database reads the inventory at T3, the inventory read is 1 because transaction 1 does not commit the transaction, so transaction 2 thinks that the inventory can be reduced currently. At time T4, transaction 1 has committed the transaction, so at time T5, when it detracts from inventory, it finds that inventory is 0, so it cannot detract from inventory. This phenomenon of inconsistent read results is called unrepeatable reads. This is one of the disadvantages of reading submitted. To overcome this shortcoming, the database isolation level proposes an isolation level for repeatable reads, which eliminates the problem of non-repeatable reads.

2.3 Repeatable

REPEATABLE READ means that after a transaction reads a record for the first time, even if other transactions modify the value of the record and commit it, when the transaction reads the record again, it still reads the value of the first time, instead of reading different data each time, which is REPEATABLE READ. Table 4 is for overcoming the non-repeatable read scenario

Table 4 Overcoming the non-repeatable read scenario

As you can see, transaction 2 tries to read the stock at T3, but the stock has already been read by transaction 1, so the database blocks it until transaction 1 commits and transaction 2 can read the value of the stock. At this point it is T5, and the value read is 0, so there is no deduction, and obviously the unreread scenario that occurs in read/write commits is eliminated. But this can lead to a new problem, which is illusory reading. Let’s take a look at table 5 for a scenario where a commodity transaction is taking place and someone in the background is doing query analysis and printing

Table 5 Illusion scenarios

This is illusory reading. First of all, the number of transactions here is not the value stored in the database, but a statistical value. The inventory of goods is the value stored in the database, which needs to be noted. Unreal reading is not for a database record, but for a number of records, such as the 51 transaction records is a number of database records statistics; While repeatable read is for a single record in the database, for example, the inventory of goods is stored by a record in the database, it can produce repeatable read, but can not produce magic read.

2.4 serialization

SERIALIZABLE is the highest level of database isolation, which requires that all SQL in the database be executed sequentially and that all operations on a record in the same database be serial. Therefore, it can ensure the consistency of data, and will not appear dirty read and phantom read phenomenon.

2.5 Summary of the four isolation levels
  • Problems such as dirty reads, unrepeatable reads, and phantom reads can occur at the READ UNCOMMITTED isolation level

  • READ COMMITTED isolation levels can cause problems such as unrepeatable reads and phantom reads, but do not cause dirty reads

  • Phantom READ problems may occur at REPEATABLE READ isolation level, but dirty and unrepeatable READ problems do not occur

  • SERIALIZABLE isolation ensures data consistency without any problems.

2.6 Use reasonable isolation levels

As an Internet developer, you need to keep in mind the various scenarios and phenomena that can occur at isolation levels when developing high-concurrency services. The isolation level of database is one of the core contents of database transaction, and also one of the key concerns of Internet enterprises. The pursuit of higher isolation levels can better ensure data consistency, but also pay the price of locking. Having locks means a loss of performance, and the higher the isolation level, the more performance drops.

So when we consider isolation levels, we consider not only data consistency, but also system performance. For example, in a scenario with high concurrency, the serial isolation level can effectively avoid data inconsistency, but it will also make the concurrent threads suspended, because only one thread can manipulate data, which will lead to a large number of threads suspended and recovered, resulting in a slow system. However, subsequent users have to wait for a long time for the response of the system, which ultimately affects user loyalty due to slow response.

Therefore, in most practical applications, the isolation level chosen is read committed, which prevents dirty reads, but inevitably unrepeatable reads and phantom reads. In order to overcome data inconsistency and performance problems, the developers of the program also designed optimistic locking, and even no longer use the relational database but other means, such as the use of non-relational database Redis as a data carrier.

The isolation level is also supported differently by different databases. For example, Oracle database only supports read committed and serialization isolation levels, while Mysql database supports all four isolation levels. The default isolation level is READ COMMITED for Oracle and REPEATABLE READ for Mysql, depending on the database.

Transaction propagation behavior

Propagation behavior is a matter of strategy adopted by invoking transactions between methods, and most of the time we assume that database transactions will either all succeed or all fail. But there are special cases in reality. For example, when executing a batch program, it will process a large number of transactions. Most of the transactions will be completed successfully, but a very small number of transactions will not be completed due to exceptions. You should not roll back other transactions called by the batch task because only a few of them failed to complete, leaving those transactions that could have completed instinctively incomplete. What we really need at this point is to call multiple transactions during the execution of a batch task, and if some transactions fail, just roll back those transactions, not the entire batch task. This allows transactions that are not problematic to be completed.

Figure 1 Propagation behavior of a transaction

In Spring, transactions can work with different strategies when one method calls another, such as creating a transaction or suspending the current transaction, which is the propagation behavior of a transaction. Still a little abstract, let’s go back to Figure 1, where the batch task is called the current method, so the batch transaction becomes the current transaction, and when it calls a single transaction, the single transaction is called a submethod. When the current method calls a submethod, each submethod is not executed in the current transaction, but a new transaction is created to execute the submethod. Let’s say that the propagation behavior of the current method call submethod is a new transaction. It is also possible to have submethods executed in a transaction-free, standalone transaction, depending entirely on the business requirements.

Definition of communication behavior

In Spring transaction, there are 7 Propagation behaviors for the database, which is defined by enumeration class Propagation.

package org.springframework.transaction.annotation;
/**** imports **/
public enum Propagation {
    /** * requires a transaction, which is the default propagation behavior * if the current transaction exists, the current transaction is used; Otherwise, create a new transaction-run submethod */
    REQUIRED(0),
    /** * support transaction; If the current transaction exists, continue to use the current transaction * if there is no transaction, continue to run the child method as no transaction */
    SUPPORTS(1),
    /** * The transaction must be used. If there is no transaction, an exception will be thrown
    MANDATORY(2),
    /** * New things are created to run submethods whether or not the current transaction exists * so that the new transaction can have a new lock and isolation level, independent of the current transaction */
    REQUIRES_NEW(3),
    /** * does not support transactions. If a transaction exists, the transaction will be suspended and the method */ will be run
    NOT_SUPPORTED(4),
    /** * does not support transactions and throws an exception if the current method has a transaction; * otherwise continue to use the transaction-free sub-method */
    NEVER(5),
    /** * if an exception occurs when a child method is called by the current method, only the SQL called by the child method is rolled back, but the transaction of the current method is not rolled back
    NESTED(6);

    private final int value;

    private Propagation(int value) {
        this.value = value;
    }

    public int value(a) {
        return this.value; }}Copy the code

There are seven propagation behaviors above, but only REQUIRED,REQUIRES_NEW and NESTED are commonly used. However, REQUIRES_NEW can have its own isolation level and locks, while QUIRES_NEW can have its own isolation level and locks.

To erase the annoying try… catch… The finally statement reduces the code for database connection closure and transaction rollback commits. Spring utilizes AOP to provide us with a convention flow for database transactions. With this convention the flow can reduce a lot of redundant code and unnecessary try… catch… Finally statement to allow developers to focus more on business development rather than database connection resources and database transactions. The resulting code is more readable and maintainable.

For transactions, you need to use annotations to tell Spring where to enable database transaction functionality. The @Transactional annotation is used for declarative transactions. This annotation can be annotated on a class or method, and when annotated on a class, it means that all public non-static methods in that class will have transaction enabled.

Many other properties can be configured in @Transactional, such as the isolation level of a transaction and propagation behavior; Another example is the exception type, so that the transaction can be rolled back to determine what exception occurred in the method without the need to roll back the transaction. These configurations are parsed out when Spring IOC loads, stored in the transaction definer (the implementation class defined by the TransactionDefination interface) class, and recorded which classes or methods need to enable transactions and which policies are used to execute transactions. All you need to do is annotate the @Transactional annotation and configure the Transactional properties of the class or method that requires the transaction.

With the @Transactional annotation, Spring knows where to enable transactions, and the convention flow is shown below:

Figure 2. Spring database transaction convention

The Transactional attributes of a database can be configured using the @Transactional annotation.

package org.springframework.transaction.annotation;
/****imports****/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    // Make the transaction manager
    @AliasFor("transactionManager")
    String value(a) default "";
    // Same as the value attribute
    @AliasFor("value")
    String transactionManager(a) default "";
    // Specify propagation behavior, which defaults to REQUIRED and requires a transaction
    Propagation propagation(a) default Propagation.REQUIRED;
    If Oracle database is used, the default is read committed; If the Mysql database is repeatable by default
    Isolation isolation(a) default Isolation.DEFAULT;
    // Timeout time, in seconds; -1 indicates unlimited
    int timeout(a) default- 1;
    // Whether it is a read-only transaction
    boolean readOnly(a) default false;
    Method returns when a specified exception occurs. By default, all exceptions are rolled back
    Class<? extends Throwable>[] rollbackFor() default {};
    The // method rolls back when the specified exception name occurs. By default all exceptions are rolled back
    String[] rollbackForClassName() default {};
    The // method does not roll back the specified exception. By default, all exceptions are rolled back
    Class<? extends Throwable>[] noRollbackFor() default {};
    The method does not roll back when the specified exception name occurs. By default, all exceptions are rolled back
    String[] noRollbackForClassName() default {};
}
Copy the code

The remarkable thing about the @Transactional annotation is that it can be placed on an interface or implementation class. The Spring team recommends putting it on the implementation class, because putting it on the interface will make your class proxy based on the interface. We know that you can use JDK dynamic proxies in Spring as well as CGLIG dynamic proxies. If you use an interface, you can’t switch to CGLIB dynamic proxies. Instead, you can only use JDK dynamic proxies and use the corresponding interface to proxy your classes to drive this annotation. This greatly limits your use, so using @transactiona annotations on your classes is the best way to implement them.

5 Reference Materials

[1] Yang Kaizhen, “SpringBoot2.x”, Chapter 6, Database transactions

[2] Turing College Lecturer Lecture Notes on Mysql Database Transactions and locks

This article first personal wechat public number, think my article to you and help readers friends. Welcome to add my personal wechat public account “Fu Talk about Java Technology stack”, push technical articles from time to time, as well as a variety of technical experts organized dry goods, including the latest interview questions and answers in 2020.