データベーストランザクションの深い理解(超詳細)

1. 事務紹介

トランザクションは、システム全体に対して操作リクエストを送信または取り消す一連の操作です。つまり、これらの操作は同時に成功するか失敗します。 

2. 取引の基本操作

2.1 トランザクション操作方法1

 例: 送金シナリオ (Zhang San が 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 トランザクション動作モード2

 オープントランザクション:
  START TRANSACTION 或 BEGIN ;
コミットトランザクション:
  COMMIT;
ロールバックトランザクション:
  ROLLBACK;

操作例:

  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 実際の開発事例 

実際の開発における一般的な例としては、銀行システムの送金業務が挙げられます。資金を移動するときは、2 つのアカウントへの送金金額と送金金額の変化がアトミックであること、つまりすべてが成功したかすべてが失敗したかを確認する必要があります。

コード スニペットの例を次に示します。

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

上記の例では、connection.setAutoCommit(false)トランザクションの自動コミットをオフにし、トランザクションのコミットとロールバックを手動で制御するオプションを使用しています。残高が十分であれば、アカウント A とアカウント B の残高を更新し、connection.commit()コミット トランザクションを使用します。例外や残高不足が発生した場合には、connection.rollback()ロールバック トランザクションを使用します。

トランザクションを使用することで、転送中のデータの一貫性と信頼性を確保できます。両方の口座の金額が正常に更新された場合にのみ、トランザクション送信操作が実行されます。それ以外の場合は、トランザクションが開始される前の状態にロールバックされます。

これは単純な例であり、実際のアプリケーションには、より複雑なビジネス ロジックやエラー処理が含まれる場合があります。ただし、この例では、実際の開発でトランザクションを使用START TRANSACTIONまたはBEGIN管理して、転送操作の一貫性と信頼性を確保する方法を示します。

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

上の例では、より多くのトランザクション構成オプションを使用しました。@Transactionalアノテーションの属性propagationはトランザクションの伝播動作を指定します。ここでPropagation.REQUIRED、現在のトランザクションが存在しない場合は新しいトランザクションを作成し、トランザクションが既に存在する場合は現在のトランザクションに追加することを意味します。isolationこの属性はトランザクションの分離レベルを指定します。ここではIsolation.READ_COMMITTED、コミットされたデータの読み取りを意味します。

移管プロセスでは、まず移管口座の残高が十分であるかどうかを確認し、不足している場合はカスタム例外がInsufficientBalanceExceptionスローされます。次に、送金口座と移入口座の残高を個別に更新し、データベースに保存します。転送プロセス中に例外が発生した場合は、それらをキャッチして処理し、カスタムTransactionFailedException例外をスローします。

このケースでは、Spring Boot でトランザクションを使用して転送操作のアトミック性を確保し、いくつかの一般的な例外を処理する方法を示します。実際のニーズに基づいて、ビジネス ロジックに基づいてさらにカスタマイズや拡張を行うことができます。

@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)これは@Transactionalアノテーションのパラメータ設定であり、トランザクションの伝播動作と分離レベルを指定するために使用されます。

  • propagation = Propagation.REQUIRED: 現在のトランザクションがない場合は新しいトランザクションを作成し、トランザクションがすでに存在する場合は現在のトランザクションに追加することを示します。これは最も一般的に使用される伝播動作であり、一連の操作が成功するかロールバックされることが保証されます。

  • isolation = Isolation.READ_COMMITTED: トランザクションの分離レベルが「読み取りコミット」であることを示します。この分離レベルでは、トランザクションはコミットされたデータのみを読み取ることができます。これにより、ダーティ読み取り (コミットされていないデータの読み取り) が回避されます。

上記の例では、@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)トランザクションの伝播動作はデフォルトに設定されPropagation.REQUIRED、分離レベルは に設定されていますIsolation.READ_COMMITTEDtransferMoneyこれは、メソッドが呼び出されるたびに新しいトランザクションが作成され (既存のトランザクションがない場合)、トランザクションはコミットされたデータのみを読み取ることができることを意味します

3. 不倫の4つの特徴

  • 原子性: トランザクションは分割不可能な最小の操作であり、すべてが成功するかすべてが失敗するかのどちらかです。
  • 一貫性: トランザクションが完了したとき、すべてのデータは一貫した状態である必要があります。
  • 分離: データベース システムによって提供される分離メカニズムにより、トランザクションは外部の同時操作の影響を受けない独立した環境で実行されます。
  • 耐久性: トランザクションがコミットまたはロールバックされると、データベース内のデータに対する変更は永続的になります。

4. 同時トランザクションの問題

質問 説明する
ダーティリード あるトランザクションが別のトランザクションからコミットされていないデータを読み取る
反復不可能な読み取り トランザクションは同じレコードを連続して読み取りますが、2 回読み取られるデータは異なります。
幻の読書 トランザクションが条件に従ってデータをクエリすると、対応するデータ行が存在しませんが、データが再度挿入されると、そのデータ行がすでに存在していることがわかります。

一般的に使用される同時実行制御メカニズムには次のものがあります。

  1. ロック: ロックを使用して、同時トランザクションによるデータのアクセスと変更を制御し、トランザクションがデータの読み取りまたは変更を行うときに、他のトランザクションが同時に同じ操作を実行できないようにします。

  2. トランザクション分離レベル (Isolation Level): さまざまなトランザクション分離レベルを設定することで、トランザクション間の可視性と同時実行制御の粒度が定義されます。

  3. マルチバージョン同時実行制御 (MVCC): 各トランザクションがデータを読み取るとき、最新のデータを直接読み取るのではなく、特定のバージョンが表示されます。これにより、ダーティ読み取り、反復不可能な読み取り、ファントム読み取りが回避されます。

  4. タイムスタンプの順序付け: タイムスタンプを使用してトランザクションを並べ替え、タイムスタンプに基づいてトランザクションの実行順序を判断し、トランザクションが正しい順序でデータを読み取り、変更することを保証します。

実際の開発においては、同時トランザクションの問題を解決するには、特定の状況に応じて適切な同時実行制御メカニズムとトランザクション分離レベルを選択し、合理的な設計と最適化を実行する必要があります。同時に、同時実行性の高い環境でもシステムがデータの一貫性と信頼性を維持できることを確認するために、十分なテストと検証も必要です。

5. トランザクション分離レベル

同時トランザクション分離レベル:

分離レベル ダーティリード 反復不可能な読み取り 幻の読書
コミットされていない読み取り
読み取りがコミットされました ×
反復読み取り(デフォルトの分離レベル) × ×
シリアル化可能 × × ×
  • √ は、現在の分離レベルで問題が発生することを示します。
  • Serializable はパフォーマンスが最も低く、Read uncommitted はパフォーマンスが最も高く、データ セキュリティは最悪です

注: トランザクション分離レベルが高くなるほど、データの安全性は高まりますが、パフォーマンスは低下します。


 トランザクション分離レベルを表示します。トランザクション分離レベルを設定します。      SESSION はセッション レベルで、現在のセッションに対してのみ有効であることを意味し、GLOBAL はすべてのセッションに対して有効であることを意味します。
  SELECT @@TRANSACTION_ISOLATION;

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

おすすめ

転載: blog.csdn.net/weixin_55772633/article/details/132131955