📖

トランザクションと排他制御(楽観ロック悲観ロック)の基礎知識✏️

2024/04/14に公開

トランザクションと排他制御の基礎知識

最近業務でこの辺について(排他制御かける悲観ロックかけるとか)実装することがあって
学んだので基礎知識と題して書いていきたいと思います!

わかりやすいように、図も用いながら書いていきたいと思います!

実際、排他制御の実装以前に自分はトランザクションについて
わかりやすく説明できるかと言われたらできない!ということに気づいたので、
トランザクションについてから書いていきたいと思います(^^)

わかりやすく書いたつもりですが、表現この方が伝わるよなとか
ちょと違うなあとかあったら、気軽にコメントしてください^^

トランザクションとは

関連する複数の処理を一つの処理として実行・管理する仕組み。
簡単に言えば、ひとまとまりの処理のこと

トランザクション処理には、4つの特性(求められる性質) "ACID" がある.

ACID特性

特性 説明
Atomicity: 原子性 すべての操作が完全に実行されるか、一切実行されないかのどちらかであることを保証する。
Consistency: 一貫性 トランザクションの実行前後でデータベースの整合性が保たれること。
Isolation: 独立性,分離性 同時に実行されても、トランザクションは互いに影響を及ぼさないこと。
Durability: 耐久性 トランザクションが完了した後も、変更は永続的で失われないこと。

Atomicity: 原子性

トランザクションは
"完全に実行されるか、一切実行されないかのどちらかであることを保証する" ということ。
"トランザクションは処理の最小単位出なくてはならない
 = 一部だけが実行されるとかあってはならない
" ということ。

なんらかでエラーが発生し失敗した場合は、"ロールバック"すること。
成功した場合は、トランザクション終了の宣言として"コミット"しDBに反映すること。

補足:"Atomic"という言葉の意味
Atomicには"最小単位"とか"不可分の"という意味があり
トランザクションは処理の最小単位出なくてはならない = 一部だけが実行されるとかあってはならない
ということ。

Consistency: 一貫性

トランザクションの実行前後でデータベースの整合性が保たれること。
→ DB全体の一貫性

Isolation: 独立性, 分離性

複数のトランザクションが同時に実行されても、トランザクションは互いに影響を及ぼさないこと。
影響を受けず独立していること。
→ トランザクションの正確性と効率性
対処法:ロックをかける = 今回書いている排他制御につながる。

でも闇雲にかければいいわけでもないし、
どの粒度でかけるのかにもよって重いシステムにもなりかねない。
この記事の後で分離レベルについて解説する

Durability: 耐久性

"トランザクションが完了した時点で、その変更は永続的であるべき" ということ

トランザクションの分離レベルについて

上記でACID原則について記載したが、その中の分離性についてくわしくした話だ。
2つ以上のトランザクションの間同士でのお互いの操作の影響度合いを定義したものを
"トランザクション分離レベル"と読んでいる。

まずは、それの理解のためにも、"リード現象(Read phenomena)"について書く。

リード現象(Read phenomena)

複数のトランザクションが同時にデータを読み取る際に生じる現象や問題のこと。

問題名 説明
ダーティリード:
Dirty Reads
まだコミットされていない他のトランザクションによって
変更されたデータを読み取ることができる問題。
ファジーリード:
Non-repeatable reads
同じトランザクション内で複数回同じクエリを実行すると、
異なる結果が得られる問題。
ファントムリード:
Phantom reads
同じクエリを複数回実行した際に、結果に含まれるデータが異なる問題。
他のトランザクションの挿入や削除によって影響を受ける。

分離レベル

分離レベルは4つに分かれる。上記で説明した問題の発生の可能性も併せて表にした。
MySQLのデフォルトはREPEATABLE READで、指定したい際は書く。

O:発生しない
×:発生し得る

分離レベル 説明 Dirty Reads Non-Repeatable Reads Phantom Reads
SERIALIZABLE トランザクションが直列化され、一貫性が最も高く、
ダーティリードやファジーリード、ファントムリードが回避される。
O O 0
REPEATABLE READ 同じクエリを実行しても、トランザクション内で読み取ったデータが変更されないようにする。
ダーティリードは回避されるが、ファジーリードやファントムリードが発生する可能性はある。
MySQLのデフォルト設定
O O X
READ COMMITTED 他のトランザクションによってコミットされたデータのみを読み取る。
ダーティリードは回避されるが、ファジーリードやファントムリードはまだ発生する可能性がある。
O X X
READ UNCOMMITTED 他のトランザクションによってまだコミットされていないデータを読み取ることが許可される。
ダーティリード、ファジーリード、ファントムリードの問題が発生する可能性がある。
X X X

上から順に処理速度は遅いが安全なもの、下に行くほど上に比べると処理速度は早いが危険になるもの

排他制御とトランザクションについて

トランザクションは、ここまで説明してきた通り
一連の操作をグループ化したひとまとまりの処理のことであり、
排他制御はそのトランザクションが複数同時にデータにアクセスしたときでも、
競合やデータの不整合が起きないよう防ぐための仕組みのこと。

排他制御(mutual exclusion)とは

同時アクセスにより不整合が発生することを防ぐため、
あるトランザクションが共有資源(データやファイル)にアクセスしている時は
他トランザクションからはアクセスできないようにして直列に処理されるように制御すること.

→簡単に言えば、同時に行われたら困るものに対策するダブルブッキング防止策!
そしてその方法は2種類ある

概要 使用場面
楽観ロック 更新対象のデータが、
データ取得時と同じ状態であることを確認してから
更新することで、データの整合性を保証する手法
- 同じ処理を複数人が同時に実行するケースが少なく、
競合が発生しづらい場合
- 処理時間が比較的短い処理の場合
悲観ロック 更新対象のデータを取得する際に
ロックをかけることことで他のトランザクションから更新されないようにする手法
- 同じ処理を複数人が同時に実行するケースが多く、
競合が発生しやすい場合
- 処理時間が長くかかる処理の場合

楽観ロックと悲観ロック

楽観ロック (Optimistic Locking)

データそのものに対してロックは行わずに、
更新対象のデータが、データ取得時と同じ状態であることを確認してから更新
することで、
データの整合性を保証する手法。
バージョンを付与し、更新時に取得したバージョンと比較する。
データ取得時のVersionとデータ更新時のVersionを同じとすることで、データの整合性を保証するもの。

これは同じ処理を複数人が同時に実行するケースが少なく、
競合が発生しづらい場合に適している。
データの更新時に他のトランザクションによる競合を回避するために、
データの状態を確認してから更新を行うものなので、
"データを取得した後に、更新に必要な情報を取得し、更新時にデータの状態を再度確認する。"
トランザクションの競合が少なければ取得した後でも問題は発生しづらいので、
速度も落ちないしこの方法が良い。

悲観ロック (Pessimistic Locking)

更新対象のデータを取得する際に(selectの時点で)ロックをかけることことで、
他のトランザクションから更新されないようにする
手法。

実装としてはSELECT ... FOR UPDATE;句の利用をする。

デッドロック

データベースのロック機能を使用する場合、
同一トランザクション内で複数のレコードを更新するとデッドロックが発生する場合がある。

二つのトランザクションが二つ以上の資源の確保をめぐって互いに相手を待つ状態となり,そこから先へ処理が進まなくなること.

排他制御によりロックされた資源に他のユーザからアクセス要求が出された時、両者は互いに使用中の資源が解放されるのをブロック状態で待つという状況が発生することがある。2つ以上のユーザ間で生じるが、この状態ではどのユーザも資源の解放を待ったまま処理が進まずに停止状態となる。 このような状態をデッドロックという。

簡単に言えば、デッドロックとは2つ以上のトランザクションが2つ以上の資源をめぐって、
お互いがロック解除待ち状態となり誰も処理が進まなくなること

補足:なぜ"資源"という表現がされるのか
"ロックかける粒度はシステムによって違う"から、一概にレコードということはできないから。


わかりやすかった資料

Discussion