データベーストランザクションを理解する
ユーザーがプラットフォームで注文を行うと、複数の処理が同時に発生します。在庫が減少し、支払い記録が作成され、注文エントリが保存されます。もしサーバーが処理の途中でクラッシュした場合、顧客には課金されたのに注文が記録されていないという事態が発生する可能性があります。データベーストランザクションは、まさにこのような部分的な失敗を防ぐために存在します。
本記事では、データベーストランザクションとは何か、ACID特性がどのように信頼性を保証するか、トランザクション分離レベルが並行アクセスをどのように制御するか、そして最新のデータベースがMVCCを使用してこれらすべてを効率的に実現する方法について説明します。
重要なポイント
- データベーストランザクションは、複数の操作を1つの作業単位としてグループ化します — すべて成功するか、すべてロールバックされるかのどちらかです。
- ACID特性(原子性、一貫性、分離性、永続性)は、トランザクションが提供する信頼性保証を定義します。
- SQLは4つの分離レベルを定義していますが、実際の動作はPostgreSQL、MySQL、SQLiteなどのデータベースエンジンによって大きく異なります。
- MVCCにより、最新のデータベースは重いロックに依存する代わりに、複数の行バージョンを維持することで、並行読み取りと書き込みを効率的に処理できます。
データベーストランザクションとは?
データベーストランザクションは、1つの作業単位として扱われる一連の操作です。すべての操作が成功して変更がコミットされるか、何かが失敗してすべてが以前の状態にロールバックされるかのどちらかです。
SQLでは、基本的なトランザクションは次のようになります:
BEGIN;
UPDATE accounts SET balance = balance - 500 WHERE id = 1;
UPDATE accounts SET balance = balance + 500 WHERE id = 2;
COMMIT;
BEGINとCOMMITの間で何かが失敗した場合、ROLLBACKによってデータベースはトランザクション開始前の状態に復元されます。ほとんどのデータベースは、トランザクション内での部分的なロールバックのためにSAVEPOINTもサポートしています。
ACID特性:実際の意味
ACIDは、トランザクションを信頼性の高いものにする一連の保証です:
- 原子性(Atomicity) — すべてのステップが成功するか、どれも成功しないかのどちらかです。部分的な更新はありません。
- 一貫性(Consistency) — データベースは1つの有効な状態から別の有効な状態に移行します。制約とルールが強制されます。
- 分離性(Isolation) — 並行トランザクションは互いに干渉しません。
- 永続性(Durability) — 一度コミットされると、変更はクラッシュしても残ります。これは通常、先行書き込みログ(WAL)を介して実装されます。
ACID準拠にはパフォーマンスコストが伴います。一部のシステムでは、厳密な保証を速度と引き換えにすることができます。そのため、データ層を設計する際にこれらの特性を理解することが重要です。
トランザクション分離レベルの説明
分離性は、4つのACID特性の中で最も微妙なものです。SQLは4つの標準的なトランザクション分離レベルを定義しており、それぞれが一貫性と並行性の間で異なるトレードオフを可能にします:
| 分離レベル | ダーティリード | 非反復読み取り | ファントムリード |
|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 |
| READ COMMITTED | 防止 | 可能 | 可能 |
| REPEATABLE READ | 防止 | 防止 | 可能 |
| SERIALIZABLE | 防止 | 防止 | 防止 |
重要: 実際の動作はデータベースエンジンによって異なります。
- PostgreSQLは
READ UNCOMMITTEDを実装していません — 暗黙的にREAD COMMITTEDにアップグレードされます。デフォルトはREAD COMMITTEDで、公式分離レベルガイドに記載されています。 - MySQL (InnoDB) のデフォルトは
REPEATABLE READです。特定のケースでギャップロックを使用してファントムリードを減らしますが、動作は分離レベルとクエリパターンに依存します。詳細はInnoDBの分離ドキュメントを参照してください。 - SQLiteは単一ライターモデルを使用しており、一度に1つの書き込みトランザクションしか実行できません。標準の分離レベルではなく、
DEFERRED、IMMEDIATE、EXCLUSIVEのトランザクションモードをサポートしています。詳細はSQLiteトランザクションドキュメントを参照してください。
データベース間で同一の動作を想定しないでください。常に使用している特定のエンジンのドキュメントを確認してください。
Discover how at OpenReplay.com.
MVCCが分離性を実用的にする方法
PostgreSQLやMySQL(InnoDB)を含むほとんどの最新のリレーショナルデータベースは、単純なロックではなく**多版型同時実行制御(MVCC)**を通じて分離性を実装しています。
MVCCでは、クエリはデータの一貫したスナップショットを参照しますが、そのスナップショットがステートメントごとに取得されるか、トランザクションごとに取得されるかは、分離レベルとデータベースエンジンに依存します。ライターは行を上書きするのではなく、新しいバージョンの行を作成します。リーダーはライターをブロックせず、ライターもリーダーをブロックしません。
これが、PostgreSQLがリーダーが常にロックを待つことなく、読み取り負荷の高いワークロードを処理できる理由です。トレードオフとして、古い行バージョンが蓄積され、クリーンアップする必要があります — PostgreSQLでは、これはVACUUMプロセスによって処理されます。
MVCCは、REPEATABLE READとスナップショット分離を大規模に実用的にするものです。
異なるシステムにおけるトランザクション
すべてのデータベースが同じ方法でトランザクションを処理するわけではありません:
- MySQL (InnoDB) は完全なACIDトランザクションをサポートしています。古いMyISAMエンジンはサポートしていません。
- PostgreSQLは堅牢なMVCCを持ち、真のシリアライザビリティ保証を伴う
SERIALIZABLE分離をサポートしています。 - SQLiteは完全にACID準拠ですが、すべての書き込みをシリアライズするため、高並行性の書き込みワークロードには不向きです。
- MongoDBはバージョン4.0でマルチドキュメントトランザクションを追加しましたが、従来のリレーショナルデータベースよりもオーバーヘッドが大きく、選択的に使用するのが最適です。
トランザクション使用の実践的ガイドライン
- トランザクションは短く保つ。 長時間実行されるトランザクションはロックを保持するか、古いMVCCスナップショットを保持し、パフォーマンスを低下させます。
- 読み取り専用クエリを不必要にトランザクションでラップしない — 特に高トラフィックのAPIでは。
- エラーを明示的に処理する。 生のSQLを書く場合でもORMを使用する場合でも、失敗時には必ず
ROLLBACKが発生するようにしてください。 - 分離レベルを意図的に選択する。
READ COMMITTEDはほとんどのWebアプリケーションにとって妥当なデフォルトです。SERIALIZABLEは正確性が要求される場合にのみ使用し、パフォーマンスへの影響をテストしてください。
まとめ
データベーストランザクションは、複数の操作にわたってデータの一貫性を保つための信頼性の高い方法を提供します。ACID特性を理解することで、どのような保証を扱っているかがわかります。分離レベルと特定のデータベースがMVCCをどのように実装しているかを知ることで、それらの保証が実際にどのようなコストを伴うか、そしてエッジケースがどこにあるかがわかります。この知識が、トランザクションを使用する開発者と、トランザクションをうまく使用する開発者を分けるものです。
よくある質問
財務計算や在庫管理など、並行トランザクションが競合する結果を生成してはならない場合に、アプリケーションが厳密な正確性保証を必要とする場合にSERIALIZABLEを使用してください。ほとんどのWebアプリケーションでは、READ COMMITTEDが一貫性とパフォーマンスの間で良いバランスを提供します。データベースエンジンによっては並行性が低下し、トランザクションの再試行が発生する可能性があるため、常に環境でSERIALIZABLEをベンチマークしてください。
ほとんどのデータベースは、各個別のSQLステートメントを暗黙的なトランザクションで自動的にラップするため、単一のINSERTまたはUPDATEはすでに原子的です。明示的なトランザクションは、複数のステートメントを1つの作業単位にグループ化し、すべてが成功するかすべてが失敗するかを保証する必要がある場合に必要になります。単独のステートメントをBEGINとCOMMITでラップすることは、意味のある利点なしにオーバーヘッドを追加します。
トランザクションロジックの周りには、常にtry-catchまたは同等のエラー処理ブロックを使用してください。トランザクション内の操作がエラーをスローした場合、ROLLBACKを発行してすべての変更を元に戻します。多くのORMやデータベースライブラリは、例外時に自動的にロールバックする組み込みのトランザクションヘルパーを提供しています。エラーを黙って飲み込むことは避けてください。データが不整合な状態になる可能性があります。
従来のロックは、リーダーとライターが互いにブロックし合うことが多く、並行性が制限されます。MVCCは、各行の複数のバージョンを保持することでこれを回避し、リーダーがライターを待つことなく一貫したスナップショットにアクセスできるようにします。トレードオフとして、古い行バージョンは定期的にクリーンアップする必要があり、PostgreSQLではVACUUMプロセスで行われます。MVCCは一般的に、読み取り負荷の高いワークロードに対してより良いスループットを提供します。
Gain control over your UX
See how users are using your site as if you were sitting next to them, learn and iterate faster with OpenReplay. — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.