トランザクション分離レベルの基礎
データベースにおけるトランザクション分離レベルは、複数のトランザクションが同時に実行される場合の一貫性と整合性を確保するために重要です。異なる分離レベルは、パフォーマンスと一貫性のトレードオフを提供しており、使用するシナリオに応じて適切なレベルを選択する必要があります。また、RDBを扱うコードを書くときはこれを意識することが必須となります。
この記事では、代表的な「トランザクション分離レベル」とそれに関連する「クエリ実行時の問題」についてまとめます。具体的な例も交えて、それぞれの特性を理解しましょう。
1. データベースのクエリ実行の際に発生しうる不具合について
ダーティリード (Dirty Read)
コミットされていないトランザクションの変更を、他のトランザクションが読み取ってしまう現象です。下の例では、T1で更新されたものの、まだ未コミットの変更を、T2が読み取ってしまいます。
<Time>-------------------------------------→
T1 ------ Update A(未コミット) --------------
T2 ------ Read A ---------------------------
このように、T2は不完全なデータを参照してしまう可能性があります。
ノンリピータブルリード (Non-repeatable Read)
同じトランザクション内で再度読み取ったデータが、他のトランザクションの更新によって変わってしまう現象です。
- T2 が最初に A を読み取り、その後もう一度 A を読み取ろうとします。
- その間に、T1 が A を更新しコミットします。
- T2 の2回目のリードでは、最初のリード時と異なるデータを取得します。
<Time>-------------------------------------------------→
T1 ------------- Update A(コミット) --------------------
T2 --- Read A -- -- Read A Again ----
ファントムリード (Phantom Read)
トランザクション内でテーブルに新しい行が追加されたり削除された場合に、再度クエリを実行したときに結果が変わってしまう現象です。
<Time>------------------------------------------------------------------------→
T1 ---------------------- |INSERT INTO A(コミット)| ---------------------------
T2 --- |SELECT * FROM A| -- -- |SELECT * FROM A Again| ---
- T2 は最初に A からデータを取得します。
- T1 が新しいレコードを A に追加し、コミットします。
- T2 が再度クエリを実行すると、新しいレコードが追加されているため、最初の結果とは異なるデータセットが返されます。
ロストアップデート(Lost Update)
同じデータに対して複数のトランザクションが同時に更新を行い、最初の更新が上書きされる現象です。
- T1 が A を読み取り、更新してコミットします。
- 同時に、T2 も A を読み取って更新しますが、T1 の変更は見えていません。
- T2 がコミットすると、T1 の更新が失われます。
<Time>-----------------------------------------------→
T1 --- |READ A| ----|UPDATE A(コミット)|--------------
T2 ---- |READ A| -----|UPDATE A(コミット)-------------
上記のような問題は、データベースのトランザクション分離レベルに応じて発生する可能性があります。
2. トランザクション分離レベル
Read Uncommitted
最も低い分離レベルであり、未コミットの変更が他のトランザクションからも見える状態です。
速度 | 発生しうる問題 |
---|---|
最も高速 | ダーティーリード、ノンリピータブルリード、ファントムリード、ロストアップデート |
Read Committed
多くのRDBでデフォルトで使用される分離レベルで、コミットされた変更のみ他のトランザクションから見えるようにします。
速度 | 発生しうる問題 |
---|---|
2番目に高速 | ノンリピータブルリード、ファントムリード、ロストアップデート |
Repeatable Read
トランザクション内で一度読み取った行のデータが、そのトランザクションが完了するまで変更されないことを保証します。
更新競合の場合どちらかのトランザクションがエラーとなります。
※PostgresSQLではRepeatableReadはSnapshotとして扱われる。
速度 | 発生しうる問題 |
---|---|
3番目に高速 | ファントムリード |
Snapshot
一部のデータベースシステム(PostgreSQLなど)で採用されている方式で、書き込み競合が少ないシステムで読み取り中心のトランザクションに向いています。
トランザクション開始時点のデータベースのスナップショットを維持し、その時点の状態でクエリを実行します。
更新競合の場合どちらかのトランザクションがエラーとなります。
速度 | 発生しうる問題 |
---|---|
4番目に高速 | - |
Serializable
最も厳格な分離レベルで、トランザクションがシリアルに実行されるかのように扱われます。その代わりに、パフォーマンスに大きな影響を与えます。
書き込み処理が多く、最高レベルのデータ整合性が要求される場合に向いています。(金融システムや在庫管理など)
更新競合の場合どちらかのトランザクションがエラーとなります。
速度 | 発生しうる問題 |
---|---|
5番目に高速 | - |
Discussion