Compreensão profunda das transações do banco de dados (superdetalhada)

1. Introdução aos assuntos

Uma transação é uma coleção de operações que enviam ou revogam solicitações de operação ao sistema como um todo, ou seja, essas operações são bem-sucedidas ou falham ao mesmo tempo. 

2. Operações básicas de transações

2.1 Método de operação de transação 1

 Exemplo: Cenário de transferência (Zhang San transfere dinheiro para 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 Modo de operação de transação 2

 Transação aberta:
  START TRANSACTION 或 BEGIN ;
Transação de confirmação:
  COMMIT;
Transação de reversão:
  ROLLBACK;

Exemplo de operação:

  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 Casos reais de desenvolvimento 

Um exemplo comum no desenvolvimento actual é o negócio de transferências no sistema bancário. Ao transferir fundos, precisamos de garantir que as alterações no montante transferido e transferido para as duas contas são atómicas, todas bem sucedidas ou todas falhadas.

Aqui está um exemplo de trecho de código:

// 假设有两个账户: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(); // 关闭数据库连接
}

No exemplo acima, usamos connection.setAutoCommit(false)a opção de desligar o commit automático de transações e controlar manualmente o commit e rollback de transações. Quando o saldo for suficiente, atualizamos o saldo da Conta A e da Conta B e usamos connection.commit()a transação commit. connection.rollback()Usamos transações de rollback se ocorrer uma exceção ou saldo insuficiente .

Ao usar transações, podemos garantir a consistência e confiabilidade dos dados durante as transferências. Somente quando os valores de ambas as contas forem atualizados com sucesso, a operação de envio da transação será executada, caso contrário, será revertida para o estado anterior ao início da transação.

Este é um exemplo simples: aplicativos reais podem envolver lógica de negócios e tratamento de erros mais complexos. START TRANSACTIONMas este exemplo mostra como utilizar ou BEGINgerir transacções no desenvolvimento real para garantir a consistência e fiabilidade das operações de transferência.

2.4 Caso de transação Springboot

@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");
        }
    }
}

No exemplo acima, usamos mais opções de configuração de transação. @TransactionalOs atributos da anotação propagationespecificam o comportamento de propagação da transação. Aqui Propagation.REQUIRED, significa que se não houver transação atual, crie uma nova transação; se já existir uma transação, adicione-a à transação atual. isolationO atributo especifica o nível de isolamento da transação. Aqui Isolation.READ_COMMITTED, significa ler os dados confirmados.

Durante o processo de transferência, primeiro verificamos se o saldo da conta de transferência é suficiente e, se for insuficiente, uma InsufficientBalanceExceptionexceção personalizada é lançada. Em seguida, atualizamos os saldos das contas de transferência e de transferência separadamente e os salvamos no banco de dados. Se ocorrerem exceções durante o processo de transferência, iremos capturá-las e tratá-las e, em seguida, lançar uma TransactionFailedExceptionexceção personalizada.

Este caso mostra como usar transações no Spring Boot para garantir a atomicidade das operações de transferência e lidar com algumas exceções comuns. Com base nas necessidades reais, você pode fazer mais personalizações e extensões com base na lógica de negócios.

@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)É @Transactionala configuração dos parâmetros da anotação, utilizada para especificar o comportamento de propagação e o nível de isolamento da transação.

  • propagation = Propagation.REQUIRED: Indica que se não houver transação atual, crie uma nova transação; se já existir uma transação, adicione-a à transação atual. Este é o comportamento de propagação mais comumente usado, que garante que um conjunto de operações será bem-sucedido ou será revertido.

  • isolation = Isolation.READ_COMMITTED: Indica que o nível de isolamento da transação é "leitura confirmada". Neste nível de isolamento, uma transação só pode ler dados confirmados. Isso evita leituras sujas (leitura de dados não confirmados).

No exemplo acima, @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)o comportamento de propagação da transação é definido como padrão Propagation.REQUIREDe o nível de isolamento é definido como Isolation.READ_COMMITTED. Isso significa que cada transferMoneyvez que um método é chamado, uma nova transação é criada (se não houver nenhuma transação existente) e a transação só pode ler dados confirmados.

3. Quatro características dos negócios

  • Atomicidade: Uma transação é uma operação mínima indivisível em que todas são bem-sucedidas ou todas falham
  • Consistência: Todos os dados devem estar em um estado consistente quando a transação for concluída
  • Isolamento: O mecanismo de isolamento fornecido pelo sistema de banco de dados garante que as transações sejam executadas em um ambiente independente que não seja afetado por operações externas simultâneas.
  • Durabilidade: Depois que uma transação é confirmada ou revertida, suas alterações nos dados do banco de dados são permanentes

4. Problemas de transações simultâneas

pergunta descrever
leitura suja Uma transação lê dados não confirmados de outra transação
leitura não repetível Uma transação lê o mesmo registro sucessivamente, mas os dados lidos duas vezes são diferentes.
leitura fantasma Quando uma transação consulta dados de acordo com as condições, não há linha de dados correspondente, mas quando os dados são inseridos novamente, verifica-se que esta linha de dados já existe.

Os mecanismos de controle de simultaneidade comumente usados ​​incluem:

  1. Bloqueio: Utilize bloqueios para controlar o acesso e modificação de dados por transações simultâneas, garantindo que quando uma transação lê ou modifica dados, outras transações não possam realizar a mesma operação ao mesmo tempo.

  2. Nível de isolamento de transação (Nível de isolamento): Ao definir diferentes níveis de isolamento de transação, são definidas a visibilidade entre as transações e a granularidade do controle de simultaneidade.

  3. Controle de simultaneidade multiversão (MVCC): quando cada transação lê dados, ela verá uma versão específica em vez de ler diretamente os dados mais recentes. Isso evita leituras sujas, leituras não repetíveis e leituras fantasmas.

  4. Ordem de carimbo de data/hora: use carimbos de data/hora para classificar transações, julgar a ordem de execução das transações com base em carimbos de data/hora e garantir que as transações leiam e modifiquem os dados na ordem correta.

No desenvolvimento real, para resolver o problema das transações simultâneas, é necessário selecionar o mecanismo de controle de simultaneidade apropriado e o nível de isolamento da transação de acordo com a situação específica, e realizar um projeto e otimização razoáveis. Ao mesmo tempo, também são necessários testes e verificações suficientes para garantir que o sistema ainda possa manter a consistência e a confiabilidade dos dados em um ambiente de alta simultaneidade.

5. Nível de isolamento de transação

Nível de isolamento de transação simultânea:

nível de isolamento leitura suja leitura não repetível leitura fantasma
Leia sem compromisso
Leitura confirmada ×
Leitura repetível (nível de isolamento padrão) × ×
Serializável × × ×
  • √ indica que o problema ocorrerá no nível de isolamento atual
  • Serializable tem o desempenho mais baixo; Read uncommitted tem o desempenho mais alto e a pior segurança de dados

Nota: Quanto maior o nível de isolamento da transação, mais seguros serão os dados, mas menor será o desempenho.


 Visualize o nível de isolamento da transação: Defina o nível de isolamento da transação:      SESSION é o nível da sessão, o que significa que é válido apenas para a sessão atual, e GLOBAL significa que é válido para todas as sessões.
  SELECT @@TRANSACTION_ISOLATION;

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

Acho que você gosta

Origin blog.csdn.net/weixin_55772633/article/details/132131955
Recomendado
Clasificación