はじめに
第2章はトランザクションの話。
ここはすでに勉強済みだったが、復習を兼ねてまとめる。
DBの話はどうしても図を使わないと説明が難しいことが多いが、
本書は文字のみで分かりやすく説明してるので凄い。。
用語
セッション
- 認証を経てデータベースに対して行う1つの接続の単位
- セッション中、ユーザは0個以上のトランザクションを実行する
トランザクション
- コンピュータで行うひとまとまりの処理のこと
- SQLにおけるINSERT/UPDATE/DELETEなどの変更はCOMMITを行うまで永続化されない
- 永続化したくない場合はROLLBACKを行うことでトランザクション処理開始時の状態に戻る
ACID性
RDBMSはデータがおかしくなったり消えたりしない為に以下の4つの特性を保証している。
1. 原子性
トランザクションは、常に全体が永続化されるか全体がロールバックされるかどちらかである。(データの一部だけが保存されたりしない)
ただし、SAVEPOINTという機能がある場合は、トランザクション内にSAVEPOINTを設定することで限定的なロールバックが可能。
SAVEPOINTの代替手段としては、トランザクションを細かく分割するか、アプリケーション側で事前に整合性チェックを行うETL処理を行うかのどちらか。
2. 一貫性
トランザクション開始時/コミット時に、全てのデータ整合性制約、参照制約が満たされている。
とはいえ、制約にはDEFERRABLEやNOT DEFERRABLE制約(遅延制約)が付けられるため、この一貫性のルールは、あくまでセッションの終了時点で適用されるものである。
3. 独立性
直列実行性とも呼ばれる。
あるトランザクションは、他の全てのトランザクションから独立しており、互いに影響を及ぼさない。
つまり、複数のトランザクション処理を直列に(順番に)実行したときと同じ結果になることが保証されている。
ただし、この独立性を完璧に守ろうとすると内部的なチェック処理が増えたり、ロックの待ち時間が増えるため処理速度に影響が出る
そのためデータベースでは、トランザクション同士の干渉をある程度許容することで、独立性と処理速度を両立させている。
この独立性のレベルのことを「トランザクション分離レベル」と呼ぶ(後述)
4. 耐久性
データベースが壊れたとしても、データが無くなったりおかしくならないこと。
これを実現するために、ロギングとバックアップの機能がある。
同時実行制御
データベースは、一般的に複数人のユーザ(処理)が同時にアクセスし、共有して使われる。
お互いが干渉せずにトランザクションを実行するためにはどうすれば良いかを考えるのが同時実行制御である。
トランザクションが干渉すると、どのようなことが起こるか。
本書で紹介されてるものを図にする。
ダーティライト
ダーティリード
反復不能読み取り
同じ問い合わせなのに同じ結果になると保証されない。(あったデータが消える、書き換わる)
ファントム
同じ問い合わせなのに同じ結果になると保証されない。(無かったデータが現れる)
失われた更新
トランザクション分離レベル
独立性にはレベルがいくつかあり、セッションごとに分離レベルを設定できる。
設定するレベルによって、上記の内いくつかのデータ不整合を避けることができる。
いずれの分離レベルもトランザクションの原子性を担保し、いかなる更新も失われない。
以下の表の○印は、発生し得る不整合を表している。
|トランザクション分離レベル|ダーティリード|反復不能読み取り|ファントム|
|:-----------|:-----------|:-----------|:-----------|:-----------|
|SERIALIZABLE||||
|REPEATABLE READ|||○|
|READ COMMITTED||○|○|
|READ UNCOMMITTED|○|○|○|
その他の分離レベル
CURSOR STABILITYはREAD COMMITTEDを拡張したもので、
READ COMMITTEDよりも強く、REPEATABLE READより弱い。
他にも
Monotonic View
Snapshot Reads
Snapshot Isolation
などもあるらしいが本書にも載っていなかった。
DB製品によってデフォルトの分離レベルも異なる上にサポートしてるものも異なるので理解したいところ。
同時実行制御の種類
同時実行制御の方法は大きく分けて2種類ある。
1.悲観的同時実行制御
- トランザクションはよく衝突する、という前提による考え方
- 問題が起こる前に回避する方法をとる
- 具体的には、必ずロックを行うことで衝突を防ぐ
2.楽観的同時実行制御
- トランザクションはあまり衝突しない、という前提による考え方
- 問題が起きてから解決する方法をとる
- 具体的にはスナップショット分離を行うことで衝突を防ぐ
ロックとラッチ
- ロックには粒度があり、細かく行うほど望ましいがコストが大きい
- テーブルロック<ページロック<行ロック
- ラッチはDBMS内部で取得される非常に短くて細かいロックのこと
- ラッチはユーザ側で明示的に取得することはできない
スナップショット分離
- 各トランザクションは開始時点のスナップショットを作成しそれらを使用する
- そのためスナップショット分離は直列性を満たしていない。
- コミット時にタイムスタンプを確認し、トランザクション開始からコミットまでの間に他のコミットがあったかを確認する。
- 他のコミットがなければ問題なくコミットできる。
- ある場合はロールバックする。
デッドロック
2つのトランザクションが、互いにロックしてるリソースの解放を待つ状態が発生し、処理も進まなくなること。(どちらも、相手が必要なリソースを解放できなくなること)
どちらかの処理をロールバックさせて解決させる。
ライブロック
ある処理がリソースを使いたくて待っているが、他の複数処理がひっきりなしにロックをかけており、永久に自分の番が回ってこない状態のこと。
関連する処理をいくつか終了させるか、ライブロックされているセッションの優先度をあげることで対応する。