Deep understanding of database transactions (super detailed)

1. Introduction to affairs

A transaction is a set of operations that submit or revoke operation requests to the system as a whole, that is, these operations either succeed or fail at the same time. 

2. Basic operations of transactions

2.1 Transaction operation method 1

 Example: Transfer scenario (Zhang San transfers money to Li Si)

  1. -- 1. 查询张三账户余额
  2. select * from account where name = '张三';
  3. -- 2. 将张三账户余额-1000
  4. update account set money = money - 1000 where name = '张三';
  5. -- 此语句出错后张三钱减少但是李四钱没有增加
  6. 模拟sql语句错误
  7. -- 3. 将李四账户余额+1000
  8. update account set money = money + 1000 where name = '李四';
  9. -- 查看事务提交方式
  10. SELECT @@AUTOCOMMIT;
  11. -- 设置事务提交方式,1为自动提交,0为手动提交,该设置只对当前会话有效
  12. SET @@AUTOCOMMIT = 0;
  13. -- 提交事务
  14. COMMIT;
  15. -- 回滚事务
  16. ROLLBACK;
  17. -- 设置手动提交后上面代码改为:
  18. select * from account where name = '张三';
  19. update account set money = money - 1000 where name = '张三';
  20. update account set money = money + 1000 where name = '李四';
  21. commit;

2.2 Transaction operation mode 2

 Open transaction:
  START TRANSACTION 或 BEGIN ;
Commit transaction:
  COMMIT;
Roll back transaction:
  ROLLBACK;

Operation example:

  1. start transaction;
  2. select * from account where name = '张三';
  3. update account set money = money - 1000 where name = '张三';
  4. update account set money = money + 1000 where name = '李四';
  5. commit;

2.3 Actual development cases 

A common example in actual development is the transfer business in the banking system. When transferring funds, we need to ensure that the changes in the amount transferred out and transferred into the two accounts are atomic, either all successful or all failed.

Here is an example code snippet:

// 假设有两个账户:accountA 和 accountB
String accountA = "A";
String accountB = "B";
double transferAmount = 100.0; // 转账金额

// 获取数据库连接
Connection connection = getConnection();
try {
    connection.setAutoCommit(false); // 设置手动提交事务

    // 查询账户 A 的余额
    double balanceA = queryBalance(accountA);

    // 查询账户 B 的余额
    double balanceB = queryBalance(accountB);

    if (balanceA >= transferAmount) { // 检查账户 A 的余额是否足够
        // 扣除账户 A 的金额
        updateBalance(connection, accountA, balanceA - transferAmount);

        // 增加账户 B 的金额
        updateBalance(connection, accountB, balanceB + transferAmount);

        connection.commit(); // 提交事务
        System.out.println("转账成功!");
    } else {
        System.out.println("转账失败:账户 A 余额不足!");
    }
} catch (SQLException e) {
    connection.rollback(); // 发生异常,回滚事务
    System.out.println("转账失败:" + e.getMessage());
} finally {
    connection.close(); // 关闭数据库连接
}

In the above example, we use connection.setAutoCommit(false)the option to turn off automatic commit of transactions and manually control the commit and rollback of transactions. When the balance is sufficient, we update the balance of Account A and Account B and use connection.commit()the commit transaction. connection.rollback()We use rollback transactions if an exception or insufficient balance occurs .

By using transactions, we can ensure data consistency and reliability during transfers. Only when the amounts of both accounts are successfully updated, the transaction submission operation will be executed, otherwise it will be rolled back to the state before the transaction started.

This is a simple example, actual applications may involve more complex business logic and error handling. START TRANSACTIONBut this example shows how to use or BEGINto manage transactions in actual development to ensure the consistency and reliability of transfer operations.

2.4 Case of Springboot transaction

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
    public void transferMoney(String fromUser, String toUser, double amount) {
        try {
            User from = userRepository.findByUsername(fromUser);
            User to = userRepository.findByUsername(toUser);

            // 检查余额是否足够
            if (from.getBalance() < amount) {
                throw new InsufficientBalanceException("Insufficient balance in the account");
            }

            // 扣除转出账户的金额
            from.setBalance(from.getBalance() - amount);
            userRepository.save(from);

            // 增加转入账户的金额
            to.setBalance(to.getBalance() + amount);
            userRepository.save(to);
        } catch (InsufficientBalanceException e) {
            // 处理余额不足的异常
            throw new TransactionFailedException("Transaction failed: " + e.getMessage());
        } catch (Exception e) {
            // 处理其他异常
            throw new TransactionFailedException("Transaction failed due to an unexpected error");
        }
    }
}

In the above example, we used more transaction configuration options. @TransactionalThe attributes of the annotation propagationspecify the propagation behavior of the transaction. Here Propagation.REQUIRED, it means that if there is no current transaction, create a new transaction; if a transaction already exists, add it to the current transaction. isolationThe attribute specifies the isolation level of the transaction. Here Isolation.READ_COMMITTED, it means reading committed data.

During the transfer process, we first check whether the balance of the transferring account is sufficient, and if it is insufficient, a custom InsufficientBalanceExceptionexception is thrown. We then update the balances of the transfer-out and transfer-in accounts separately and save them to the database. If exceptions occur during the transfer process, we will catch and handle them, and then throw a custom TransactionFailedExceptionexception.

This case shows how to use transactions in Spring Boot to ensure the atomicity of transfer operations and handle some common exceptions. Based on actual needs, you can make more customizations and extensions based on business logic.

@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)It is @Transactionalthe parameter configuration of the annotation, used to specify the propagation behavior and isolation level of the transaction.

  • propagation = Propagation.REQUIRED: Indicates that if there is no current transaction, create a new transaction; if a transaction already exists, add it to the current transaction. This is the most commonly used propagation behavior, which ensures that a set of operations will either succeed or be rolled back.

  • isolation = Isolation.READ_COMMITTED: Indicates that the isolation level of the transaction is "read committed". At this isolation level, a transaction can only read committed data. This avoids dirty reads (reading uncommitted data).

In the above example, @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)the transaction propagation behavior is set to default Propagation.REQUIREDand the isolation level is set to Isolation.READ_COMMITTED. This means that each transferMoneytime a method is called, a new transaction is created (if there is no existing transaction), and the transaction can only read committed data.

3. Four characteristics of affairs

  • Atomicity: A transaction is an indivisible minimum operation. Either all of them succeed or all of them fail.
  • Consistency: All data must be in a consistent state when the transaction is completed
  • Isolation: The isolation mechanism provided by the database system ensures that transactions run in an independent environment that is not affected by external concurrent operations.
  • Durability: Once a transaction is committed or rolled back, its changes to the data in the database are permanent

4. Concurrent transaction issues

question describe
dirty read One transaction reads uncommitted data from another transaction
non-repeatable read A transaction reads the same record successively, but the data read twice is different.
phantom reading When a transaction queries data according to conditions, there is no corresponding data row, but when the data is inserted again, it is found that this row of data already exists.

Commonly used concurrency control mechanisms include:

  1. Locking: Use locks to control access and modification of data by concurrent transactions, ensuring that when a transaction reads or modifies data, other transactions cannot perform the same operation at the same time.

  2. Transaction isolation level (Isolation Level): By setting different transaction isolation levels, the visibility between transactions and the granularity of concurrency control are defined.

  3. Multi-version Concurrency Control (MVCC): When each transaction reads data, it will see a specific version instead of reading the latest data directly. This avoids dirty reads, non-repeatable reads and phantom reads.

  4. Timestamp Ordering: Use timestamps to sort transactions, judge the execution order of transactions based on timestamps, and ensure that transactions read and modify data in the correct order.

In actual development, in order to solve the problem of concurrent transactions, it is necessary to select the appropriate concurrency control mechanism and transaction isolation level according to the specific situation, and carry out reasonable design and optimization. At the same time, sufficient testing and verification are also required to ensure that the system can still maintain data consistency and reliability in a high-concurrency environment.

5. Transaction isolation level

Concurrent transaction isolation level:

isolation level dirty read non-repeatable read phantom reading
Read uncommitted
Read committed ×
Repeatable Read (default isolation level) × ×
Serializable × × ×
  • √ indicates that the problem will occur under the current isolation level
  • Serializable has the lowest performance; Read uncommitted has the highest performance and the worst data security

Note: The higher the transaction isolation level, the more secure the data, but the lower the performance.


 View the transaction isolation level: Set the transaction isolation level:      SESSION is the session level, which means it is only valid for the current session, and GLOBAL means it is valid for all sessions.
  SELECT @@TRANSACTION_ISOLATION;

  SET [ SESSION | GLOBAL ] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE };

Guess you like

Origin blog.csdn.net/weixin_55772633/article/details/132131955