トランザクション
トランザクションとは
データベースが読み書き等の処理を複数まとめて一連の情報を処理することをいう。
ACID 特性
ACID 特性とはトランザクションが備えるべき 4 つの特性の頭文字を取ったものである。
原子性 (Atomiicity)
原子性はトランザクションは「全て実行された」状態か「全く実行されていない」状態のどちらかの状態
である。
要するに途中で処理が中断されているものは存在しないということである。(ALL or Nothing)
原子性は全ての処理が完了した場合に COMMIT し、どこか一つでも失敗すると ROLL BACK を行うことで担保することができる。
一貫性 (Consistency)
一貫性はトランザクションはデータベースの内容に矛盾を生じさせない
である。
要するに実行前と実行後の結果に整合性があることが必要である。
独立性 or 隔離性 (Isolation)
独立性(隔離性)はトランザクションは他のトランザクションの影響を受けない(与えない)
である。
独立性にはレベルがあり、他のトランザクションに影響を与える度合いを分離レベルと呼び、
トランザクション分離レベルに詳細は記載している。
耐久性 (Durability)
耐久性は正常に実行されたトランザクションの結果はその後障害が発生しても保持される
である。
要するに障害発生後に回復できるようにする必要がある。
排他制御
一貫性と独立性を担保するために複数のトランザクションが同時に実行された場合に制御する必要がある。
この時に、複数のトラザクションを実行した場合にトランザクションを完全に独立して順番に実行した結果と同じになることを
直列可能性
といい、この性質を満たすように制御することを同時実行制御
という。
この同時実行制御の方法としてロックを用いてデータベースの資源を制御する方法を排他制御
という。(ロックされた資源を排他資源という)
ロックのかけ方には 2 種類存在する。
-
共有ロック
データを参照する時だけにかけるロックであり、他のトランザクションからもデータを参照することができる。しかし、データの更新は行えない。
-
専有ロック
共有ロックとは異なり、他のトランザクションからデータの参照もできなくなる。他のトランザクションはロックが解放されるまで待機する。
ロックの組み合わせは以下の表になる。
共有ロック | 専有ロック | |
---|---|---|
共有ロック | 共存可能 | 共存不可能 |
専有ロック | 共存不可能 | 共存不可能 |
ロックには粒度が存在する。特定の行のみにロックをかけるのかテーブル全体にロックをかけるかといったものである。
対象の行のみロックすることを行ロック
といい、テーブル全体をロックすることをテーブルロック
という。
排他制御を行うことで一貫性と独立性を保つことが可能である。しかし、ロックをかけるタイミングによってデッドロック
と呼ばれる状態に陥ることがある。
デッドロックとは 1 つのトランザクション内で 2 つ以上のデータにロックをかけるトランザクション処理が複数同時に発生した場合にお互いのトランザクションが 資源の解放待ちを行い、処理が停止してしまう現象をいう。
それぞれのトランザクションの初めの処理で A, B ともにテーブルがロックされている。 その後、トランザクション 1,2 ともに別のトランザクションがロックしているテーブルをロックしようと処理が行われると、テーブルはすでにロックされているので解放されるのを待つ。 しかし、お互いの次に必要な処理のテーブルがお互いによってロックされているのでこれ以上処理が進むことがない。
デッドロックを事前に検知する方法として待ちグラフが使用される。
待ちグラフとは実行中のトランザクションを各ノード、データのアンロック待ちの様子を矢印(有向辺)で表現したグラフである。 平成 29 年 応用情報技術者試験 秋 午前 29 問で 待ちグラフが出題されている。
排他制御の実現方法は複数存在するが、
- 楽観的ロック
- 悲観的ロック
の 2 つ実現方法が代表的である。それぞれの方法で開発コスト・競合を検知できるタイミング等が異なる。
楽観的ロック
楽観的(オプティミスティック)ロックはほとんど同時更新なんて起きないだろうという前提のもと考えられた実現方法である。
方法はデータを取得し、処理を行った後、処理開始時に取得した時とデータが同じ状態であったかを確認し、同じ状態であればデータを更新を行う。
悲観的ロック
悲観的(ペシミスティック)ロックは楽観的ロックとは反対で、頻繁に同時更新が行われるという前提のもと考えられた実現方法である。
方法はデータを取得する際にデータをロックすることで他のトランザクションからのアクセスを防ぎ、データの更新を終了するとロックを解放する。
ただし、PC が強制的にダウンするなどの問題が生じた際にロックが解除されないといったことが生じる可能性がある。
トランザクション分離レベル
トランザクションの独立性を完全に満たすには同時実行数を 1 とし、1 度に 1 つのトランザクションしか動作させないようにするしかない。 しかし、1 度に 1 つのトランザクションしか動作しないのであれば、処理速度が遅くなってしまう。そのため、同時実行制御では、 独立性のレベル(分離レベル)をいくつか設定し、処理速度と独立性(データの整合性)を業務に合わせて設定できるようにしている。
トランザクション分離レベルには次の 4 つのレベルが設定されている。
-
READ UNCOMMITTED (未コミット読み取り)
他のトランザクションのコミットされていないデータも読み取る。
-
READ COMMITTED (コミット読み取り)
他のトランザクションのコミットされているデータのみを読み取る。
-
REPEARABLE READ (反復読み取り)
トランザクションの実行中は読み取ったデータは何度読み込んでも必ず一致することを保証する。
-
SERIALIZABLE (直列化)
必ず直列可能性を満たすようにトランザクションを同時実行制御を行う。
上から順に独立性が低く、処理速度が早くなっている。1 3 のトランザクション分離レベル次のような問題が生じることがある。
- ダーティリード
- ノンリピータブルリード
- ファントムリード
- (ロストアップデート)
これらの問題が生じる分離レベルを表にすると以下のようになる。
ダーティリード | ノンリピータブルリード | ファントムリード | ロストアップデート | |
---|---|---|---|---|
READ UNCOMMITTED | ||||
READ COMMITTED | ||||
REPEARABLE READ | ||||
SERIALIZABLE |
ロストアップデートは排他制御を行っている以上は生じない問題である。
ダーティリード
変更途中のデータを読み取り、その変更途中のトランザクションがロールバックした場合に変更途中のデータを読み取ったトランザクションは誤ったデータを取得したことになる。 そのため、誤った情報がデータベースに記録されてしまうため、データ整合性が損なわれる問題。
ノンリピータブルリード
コミット(確定)したデータのみを読み取るが、別トランザクションのコミット前ではコミット前のデータ、 コミット後はコミット後のデータを読み取る。 そのため、1 つのトランザクション内でデータを読み取るタイミング次第で値が変更してしまい、データ整合性が損なわれる問題。
ファントムリード
トランザクション中にデータが新しく追加され、そのデータを扱うことでデータ整合性が損なわれる問題。
ロストアップデート
更新したデータが失われてしまい、データ整合性を損なわれる問題。
ログ
ACID 属性の耐久性を保つために使用され、更新前ログと更新後ログの 2 種類が存在する。
CPU はメモリ上のデータしか操作ができない。そのため、データベースを利用する際はハードディスクからデータの読み込みが行われる。
データを更新してすぐにハードディスクデータが記録されるのではなく、チェックポイント
と呼ばれる書き込みを行うポイントでハードディスクに書き込みを行う。
チェックポイントから次のチェックポイントの間に障害が発生した場合は、変更がハードディスクに記録されていないので、変更が失われてしまう。
そのため、ログにデータの変更を記録し、ログからデータを復元する。
ログも適度なタイミングでハードディスクに出力されるが、データとは異なり、コミットした瞬間にハードディスクにも記録する。
障害が発生した際の状況として以下の図のような 2 通りの状態が考えられる。
- トランザクション中にチェックポイントを迎え、次のチェックポイントの前に障害が発生 (図左)
- トランザクションが完了し、コミットしたデータがチェックポイントを迎える前に障害が発生 (図右)
1.は更新前ログを用いてロールバック(後退復帰)
を行い、トランザクションを実施する前の状態に戻し、データの整合性を保つ。
2.は更新後ログを用いてロールフォワード(前進復帰)
を行い、トランザクション実施後の状態に戻し、データの整合性を保つ。
WAL
ログの書き込みはメモリ上のデータの更新とともに行われる。そのため、更新時の書き込み処理として
- 更新前ログの書き出し
- 実データの更新
- 更新後ログの書き出し
の 3 つが存在する。この 3 つをどの順番で行うか特に定められていないが、1,2,3 の順に行うのが自然に見える。 しかし、2,3 の間で障害が発生した場合に 2 のデータは更新されたのどうかが分からなくなってしまう。 その問題を解決するために、1,3 のログ部分を先に書き出し、その後に 2 を行うようにすることでこのような問題が生じなくなる。
このように先にログを書き出す方法を WAL(Write Ahead Log)という。