トランザクション分離レベルとは、データベースにおいて複数のトランザクションが同時に実行される際に、各トランザクションが互いの変更をどの程度「見えるか」を制御する設定のことです。
ACID特性の分離性(Isolation)を具体的にどのレベルで実現するかを定義するものであり、READ UNCOMMITTED・READ COMMITTED・REPEATABLE READ・SERIALIZABLEという4つのレベルがSQL標準で定義されています。
分離レベルが高いほどデータの整合性は向上しますが、同時実行性能(スループット)は低下するというトレードオフがあります。
本記事では、トランザクション分離レベルとは何か、4つのレベルと特徴、READ UNCOMMITTED・READ COMMITTED・REPEATABLE READ・SERIALIZABLE・ロック機能などについてわかりやすく解説していきます。
データベース設計やパフォーマンスチューニングに携わる方にとって、分離レベルの正確な理解は不可欠な知識です。
トランザクション分離レベルは整合性と性能のトレードオフを制御するデータベース設定
それではまず、トランザクション分離レベルの概念と、分離レベルが低い場合に発生する可能性のある問題現象について解説していきます。
トランザクション分離レベルを理解するためには、まず分離レベルが不十分な場合に発生する3種類の問題現象を知ることが重要です。
これらの問題現象とは、ダーティリード(Dirty Read)・ノンリピータブルリード(Non-Repeatable Read)・ファントムリード(Phantom Read)の3つであり、各分離レベルがどの問題を防ぐかによって特徴が定義されます。
3つの問題現象の詳細
ダーティリード(Dirty Read)とは、あるトランザクションがまだコミットしていない変更内容を、別のトランザクションが読み取ってしまう問題です。
コミットされていない変更はロールバックされる可能性があるため、ダーティリードを起こすと実際には存在しないはずのデータを読み込んでしまいます。
ノンリピータブルリード(Non-Repeatable Read)とは、同一トランザクション内で同じ行を2回読み取った際に、別のトランザクションによる更新が間に入ることで異なる値が返される問題です。
ファントムリード(Phantom Read)とは、同一トランザクション内で同じ条件のSELECTを2回実行した際に、別のトランザクションによるINSERT/DELETEが間に入ることで行数が変わってしまう問題です。
ファントムリードの例:
トランザクションA:SELECT COUNT(*) FROM orders WHERE status=’pending’; → 10件
(この間にトランザクションBがINSERT INTO orders… COMMITを実行)
トランザクションA:SELECT COUNT(*) FROM orders WHERE status=’pending’; → 11件
同じトランザクション内で件数が変わってしまう
4つの分離レベルと問題現象の対応
| 分離レベル | ダーティリード | ノンリピータブルリード | ファントムリード |
|---|---|---|---|
| READ UNCOMMITTED | 発生する | 発生する | 発生する |
| READ COMMITTED | 防止 | 発生する | 発生する |
| REPEATABLE READ | 防止 | 防止 | 発生する(DBによって防止) |
| SERIALIZABLE | 防止 | 防止 | 防止 |
READ UNCOMMITTEDの特徴
READ UNCOMMITTEDは最も低い分離レベルであり、コミットされていない変更もほかのトランザクションから読み取れる状態です。
ダーティリードが発生するため、データの正確性が求められる通常のアプリケーションではほとんど使用されません。
非常に高い読み取り性能が得られるため、近似値で十分な集計処理や監視用のダッシュボードなど、多少の不正確さが許容できる場面での利用に限定されます。
READ COMMITTEDの特徴
READ COMMITTEDはコミット済みのデータのみを読み取ることができる分離レベルです。
ダーティリードは防止できますが、同一トランザクション内での読み取り結果が変わるノンリピータブルリードは発生します。
PostgreSQLのデフォルト分離レベルはREAD COMMITTEDであり、多くのWebアプリケーションはこのレベルで問題なく動作します。
一般的なオンライン処理(OLTP)システムで最もバランスの良い選択肢として広く使われています。
REPEATABLE READの特徴
REPEATABLE READは同一トランザクション内で同じ行を何度読み取っても同じ値が返されることを保証する分離レベルです。
ダーティリードとノンリピータブルリードの両方を防止できますが、SQL標準ではファントムリードは防止の対象外とされています。
ただしMySQLのInnoDBはREPEATABLE READでもMVCCとネクストキーロックによってファントムリードを防止します。
MySQLのデフォルト分離レベルはREPEATABLE READです。
SERIALIZABLEの特徴
SERIALIZABLEはすべての問題現象を防止する最も厳格な分離レベルです。
トランザクションを順番に(直列に)実行した場合と同じ結果が得られることを保証します。
高い整合性を提供しますが、排他ロックが広範囲に設定されるため同時実行性が大幅に低下し、デッドロックが発生しやすくなります。
金融取引の最終決済処理など、絶対的な整合性が求められる処理に限定して使用するのが一般的でしょう。
分離レベルとロックメカニズムの関係
続いては、各分離レベルがどのようなロックメカニズムによって実現されるかを確認していきます。
分離レベルの実現方式はデータベース製品によって異なりますが、主にロックベースとMVCC(マルチバージョン同時実行制御)の2方式があります。
MVCCによる分離レベルの実現
MVCC(Multi-Version Concurrency Control)は、データの各バージョンをタイムスタンプやトランザクションIDで管理し、トランザクションが開始した時点のスナップショットを読み取ることで、ロックを使わずに分離性を実現する仕組みです。
PostgreSQLとMySQLのInnoDBはMVCCを採用しており、読み取り操作が書き込みをブロックしないため高い同時実行性を実現しています。
MVCCでは古いバージョンのデータ(Undo log)を保持することでスナップショットを提供するため、長時間のトランザクションがUndo logの肥大化を引き起こすという注意点があります。
デッドロックと分離レベルの関係
分離レベルが高いほどロックの範囲と保持時間が長くなるため、デッドロックが発生しやすくなります。
デッドロックとは、複数のトランザクションが互いに相手のロックが解放されるのを待ち続けて処理が止まってしまう状態です。
データベースはデッドロックを定期的に検出し、一方のトランザクションを強制的にロールバックして解消します。
デッドロックを減らすためには、複数のテーブルにアクセスする場合に常に同じ順序でアクセスすること・トランザクションをできるだけ短く保つことが有効です。
まとめ
本記事では、トランザクション分離レベルとは何か、4つのレベルと特徴、READ UNCOMMITTED・READ COMMITTED・REPEATABLE READ・SERIALIZABLE・ロック機能などについて解説しました。
トランザクション分離レベルはダーティリード・ノンリピータブルリード・ファントムリードという3種類の問題現象とのトレードオフで定義されており、アプリケーションの要件に応じた適切なレベルの選択が重要です。
多くのWebアプリケーションではREAD COMMITTEDまたはREPEATABLE READが適切な選択肢であり、絶対的な整合性が必要な金融処理などではSERIALIZABLEを限定的に活用することが推奨されます。
MVCCとロックのメカニズムを理解したうえで、デッドロックのリスクも考慮しながら適切な分離レベルを選択することが、高性能で信頼性の高いデータベースアプリケーション設計の鍵となるでしょう。