DBトランザクション

# はじめに
こんにちは！エアークローゼットのMDです。この記事は、エアークローゼットアドベントカレンダー2024の23日目の記事です。

開発者として、日々リレーショナルデータベースを使って仕事をしています。今回は、トランザクションについて少し深掘りして紹介したいと思います。

## トランザクションレベル
トランザクションレベルは、データベースにおけるトランザクションの分離レベルを指します。主なトランザクション分離レベルには以下の4つがあります：

1. Read Uncommitted: 他のトランザクションがコミットしていない変更も読み取ることができます。データの一貫性が低い。
    ```mermaid
    sequenceDiagram
    participant C as Client
    participant DB as Database
    participant OC as Other Clients
    
    C ->> DB: START TRANSACTION 
    
    OC ->> DB: START TRANSACTION INSERT INTO table (..., age) VALUES (..., 25) (new records)
    C ->> DB: SELECT * FROM table WHERE age > 20
    DB ->> C: Return  data (include new records)
    OC ->> DB: COMMIT
    
    C ->> DB: COMMIT
    ```
2. Read Committed: 他のトランザクションがコミットした変更のみ読み取ることができます。ダーティリードを防ぎます。
    ```mermaid
    sequenceDiagram
    participant C as Client
    participant DB as Database
    participant OC as Other Clients
    
    C ->> DB: START TRANSACTION 
    
    OC ->> DB: START TRANSACTION INSERT INTO table (..., age) VALUES (..., 25) (new records)
    C ->> DB: SELECT * FROM table WHERE age > 20
    DB ->> C: Return  data (not include new records)
    OC ->> DB: COMMIT
    
    C ->> DB: COMMIT
    ```
3. Repeatable Read: トランザクションが開始された時点のデータを読み続けます。何回読み込みを行っても同じ結果を返却します（他のトランザクションの同じデータ修正を防がない）
    ```mermaid
    sequenceDiagram
    participant C as Client
    participant DB as Database
    participant OC as Other Clients

    Note over C,DB: Repeatable Read Isolation
    
    C->>DB:　START TRANSACTION 
    
    Note over DB:　committed データのsnapshot生成
    
    C->>DB: SELECT * FROM table WHERE id = 1
    DB->>C: Returns data (version 1)
    
    Note over OC,DB: データ更新
    OC->>DB: UPDATE table SET value = 'new' WHERE id = 1
    DB->>OC: Update success
    
    C->>DB: SELECT * FROM table WHERE id = 1
    Note over DB: トランザクション開始時のsnapshotデータ返却
    DB->>C: Returns same data (version 1)
    
    C->>DB: COMMIT
    ```
4. Serializable: 完全に同時実行を防ぎ、他のトランザクションが同じデータにアクセスするのを防ぎます。最も高い一貫性を提供しますが、パフォーマンスに影響を与える可能性があります。
    ```mermaid
    sequenceDiagram
    participant C1 as Client 1
    participant C2 as Client 2
    participant DB as Database

    Note over C1,DB: Serializable Transaction 1
    C1->>DB: BEGIN TRANSACTION
    
    Note over C2,DB: Serializable Transaction 2
    C2->>DB: BEGIN TRANSACTION
    
    C1->>DB: SELECT * FROM accounts WHERE id = 1
    Note over DB: 読み取りロック
    DB->>C1: Returns balance = $100
    
    C2->>DB: UPDATE accounts SET balance = 50 WHERE id = 1
    Note over DB: Blocked by read lock from T1
    
    C1->>DB: UPDATE accounts SET balance = 75 WHERE id = 1
    Note over DB: 書込ロック
    DB->>C1: Update successful
    
    C1->>DB: COMMIT
    Note over DB: ロック解放
    
    Note over C2,DB: Transaction 2開始可能
    DB->>C2: Error: Serialization conflict
    Note over C2,DB: ロールバック
    C2->>DB: ROLLBACK

    Note right of DB: キーポイント:
    Note right of DB: 1. 読み取りロックは書込作業を防ぎ
    Note right of DB: 2. 書込ロックは読み取りを防ぎ
    Note right of DB: 3. トランザクションは順番に実行
    ```

## OptimisticロックとPessimisticロック
トランザクションロック仕組みも以下2つの種類があります
### Optimisticロック（楽観的ロック）
Optimisticロックは、データの競合が少ないと仮定して動作します。トランザクションがデータを読み込み、更新時にそのデータが他のトランザクションによって変更されていないことを確認します。変更されていれば、トランザクションはロールバックされ、再試行されます。

メリット: スケーラビリティが高く、パフォーマンスが良い。
デメリット: データの競合が発生した場合、再試行が必要となり、パフォーマンスが低下する可能性があります。

```mermaid
    sequenceDiagram
    participant C1 as Client 1
    participant C2 as Client 2
    participant DB as Database

    Note over C1,DB: Transaction 1
    C1->>DB: SELECT * FROM products WHERE id = 1
    DB->>C1: Returns (data, version=1)
    
    Note over C2,DB: Transaction 2 (concurrent)
    C2->>DB: SELECT * FROM products WHERE id = 1
    DB->>C2: Returns (data, version=1)
    
    C1->>DB: UPDATE products SET quantity = 50 WHERE id = 1 AND version = 1
    Note over DB: Version matches, update succeeds
    DB->>DB: Increment version to 2
    DB->>C1: Update successful
    
    C2->>DB: UPDATE products SET quantity = 75 WHERE id = 1 AND version = 1
    Note over DB: Version mismatch (expected 1, found 2)
    DB->>C2: Update failed - Stale data
    
    Note over C2: Needs to retry operation
    C2->>DB: SELECT * FROM products WHERE id = 1
    DB->>C2: Returns (new data, version=2)
    C2->>DB: UPDATE products SET quantity = 75 WHERE id = 1 AND version = 2
    DB->>C2: Update successful
```    

### Pessimisticロック（悲観的ロック）
Pessimisticロックは、データの競合が頻繁に発生すると仮定して動作します。トランザクションが開始されると、対象のデータをロックします。他のトランザクションはそのデータにアクセスできなくなります。トランザクションが完了すると、ロックが解除されます。

メリット: データの一貫性が保証されます1。
デメリット: スケーラビリティが低く、パフォーマンスが低下する可能性があります。デッドロックが発生する可能性もあります。

```mermaid
sequenceDiagram
    participant C1 as Client 1
    participant C2 as Client 2
    participant DB as Database

    Note over C1,DB: Transaction 1
    C1->>DB: BEGIN TRANSACTION
    C1->>DB: SELECT * FROM products WHERE id = 1 FOR UPDATE
    Note over DB: Acquires exclusive lock
    DB->>C1: Returns data
    
    Note over C2,DB: Transaction 2 (concurrent)
    C2->>DB: BEGIN TRANSACTION
    C2->>DB: SELECT * FROM products WHERE id = 1 FOR UPDATE
    Note over DB: Waits for lock release
    
    C1->>DB: UPDATE products SET quantity = 50 WHERE id = 1
    DB->>C1: Update successful
    C1->>DB: COMMIT
    Note over DB: Releases lock
    
    Note over C2,DB: Transaction 2 can now proceed
    DB->>C2: Returns data
    C2->>DB: UPDATE products SET quantity = 75 WHERE id = 1
    DB->>C2: Update successful
    C2->>DB: COMMIT
```

ではどちらを選ぶべきか
Pessimisticロック: データの競合が多い場合や、データの一貫性が非常に重要な場合に適しています（例：銀行口座の振込）。（InnoDBエンジン系のデータベースのデフォルトオプションです）
Optimisticロック: データの競合が少ない場合や、競合が発生しても再試行のコストが低い場合に適しています（例：オンラインショッピングカート・読み取りが多い）。
どちらのロック方式を選ぶかは、アプリケーションの要件やトランザクションの特性に応じて決定する必要があります。


## まとめ
以上、DBのトランザクションについて紹介しました。
トランザクションに挟んで実行するではなく、ユースケースに適した機能を使って開発しましょう。

## P.S.
最後まで読んでいただき、ありがとうございます！

エアークローゼットアドベントカレンダー2024はまだまだ続きますので、ぜひ他のエンジニア、デザイナー、PMの記事もご覧いただければと思います

また、エアークローゼットはエンジニア採用活動も行っておりますので、興味のある方はぜひご覧ください！