Love Beautiful Code
分離レベルについて理解したい
2022/8/22
トランザクションについて
何はともあれ、RDBMSのトランザクションについて理解していないとお話にならないので、まずはそこの説明から記述します。 トランザクション(transaction)とは、1つの作業単位として不可分に扱われるSQLクエリの集まりのことです。噛み砕くと、複数のSQLを「1セット」として実行して、SQLの全てがデータベースに適用できる場合にはデータベースに適用し、もし途中でクラッシュ等で処理が中断した場合には、すべてのSQLを適応させない一連のプロセスのことを指します。つまり、全てが適用されるか、全てが適用されないかのどちらかになる。
例
2つの口座A, Bを持っていて、Aから200万円をBに移した場合の一連の処理は以下の通りである。
- Aに200万円が入っていることを確認する。
- Aから200万円を引く
- Bに200万円を足す
これらを一つのトランザクションで行うことでどこかで処理が失敗してもロールバックすることによってもとに戻すことができる。つまり、 200万円が足されずに引かれたままになる
ということは起きえない。
ACID
また、トランザクションは下記4つのACIDテストを満たさない限り、トランザクションが十分であるとは言えない。 下記に実践ハイパフォーマンスMySQLより引用する
不可分性 (Atomicity)
トランザクションは、トランザクション全体が適用またはロールバックされるように、目に見えない1つの作業単位として機能しなければならない。トランザクションが不可分である場合、部分的に完了するトランザクションのようなものは存在しない。すべて完了するか、全く完了しないかのどちらかである。
つまり、トランザクションは分割できない処理の一塊であることを満たす
一貫性 (Consistency)
データベースは常に、ある一貫した状態から次の一貫した状態に遷移すべきである。 (中略) トランザクションは決してコミットされないため、トランザクションの変更は1つもデータベースに反映されない。
主に、システムのクラッシュから保護する。また、トランザクションの結果が整合性を保つことが保証されていること。
分離性 (Isolation)
通常、トランザクションの結果はトランザクションが完了するまでの他のトランザクションからは見えない。
2つのトランザクションが同じテーブルに変更をかけた場合に、お互いのトランザクションからは結果が見えないということ。 分離レベルとも大きく関わってくるのでその時に詳しく述べる。
永続性 (Durability)
コミットされたトランザクションの変更は確定される。つまり、システムがクラッシュしたとしてもデータが失われないように、変更は記録しなければならない。ただし、実際には多くのレベルに分かれていることから、永続性はややあいまいな概念である。他よりも高い安全性を保証する永続性戦略もあるが、100パーセントの永続性というものは存在しない。
コミットされるとトランザクション内の変更が確定され、データベースに適応される。逆にいうと、コミットされるまではデータベースには適応されない。
分離レベル
ここからようやく分離レベルの話をしていきます。 SQLの規格として4つのレベルが定義されている。一般に分離レベルが低いほど並行性が高くなり、オーバーヘッドが低くなる
※ オーバーヘッド: 削除・更新などの繰り返しで出るデータのゴミ
分離レベルごとに起きる問題
ダーティリード
コミットされていないデータを読むことができること。 A, Bのトランザクションで、Aで変更されたが、コミットされていない内容をBから参照することができる状態です。
ノンリピータブルリード
同じSQL分を2回実行すると異なるデータが返される可能性があるという状態です A, Bのトランザクションがあることを想定する
- トランザクションAでSELECT文を実行し、データを取得する
- トランザクションBでテーブルを
更新
- トランザクションAでSELECT分を実行すると、1の結果と異なるデータが取得される可能性がある
ファントムリード
トランザクション中に別のトランザクションでINSERT文が実行されると、その前後でトランザクション内でデータを取得すると別トランザクションでインサートしたデータが見えてしまうという状態 例に漏れずA, Bのトランザクションが存在すると場合、
- トランザクションAでSELECT文を実行し、データを取得する
- トランザクションBでテーブルを
追加
- トランザクションAでSELECT文を実行すると、トランザクションBで追加したデータが見えてしまう
READ UNCOMMITTED
コミットされていないトランザクションの結果が他のトランザクションから見える。 つまり、ダーティリードが発生する。
READ COMMITTED
MySQLを除くほとんどのデータベースシステムのデフォルトの分離レベル。 トランザクションに見える内容は、開始時にすでにコミットされいるデータのみであり、トランザクション内の変更は、コミットするまでは他のトランザクションからは見えない。しかし、この分離レベルはノンリピータブルリードを許容している。
REPEATABLE READ
トランザクションが読み取るすべての行が、同じトランザクション内でのそれ以降の読み取りで「同じに見える」ことが保証されている。しかし、理論的には、ファントムリードが問題が発生する。InnoDBなどのストレージエンジンではMVCC(MultiVesion Concurrency Control)に基づいて、この問題を解決している
SERIALIZABLE
最も高い分離レベルで、競合が起こらないようにトランザクションを強制的に順序づけを行い、ファントムリードの問題を解決している。この分離レベルでは、読み取るすべての行にロックをかける。したがって、タイムアウトやロック競合が頻発する可能性がある
まとめ
分離レベル | ダーティリード | ノンリピータブルリード | ファントムリード | 読み取りロック |
---|---|---|---|---|
READ UNCOMMITTED | ○ | ○ | ○ | × |
READ COMMITTED | × | ○ | ○ | × |
REPEATABLE READ | × | × | ○ | × |
SERIALIZABLE | × | × | × | ○ |