2種類のロック:共有ロック-占有ロック(排他ロック)
- 共有ロック
- データを参照していることを示すためのロックで、SELECT文に付加できる
- ロックされたレコードは更新できなくなる。参照は可能(今見てるから更新しないで~)
- 同一レコードに対して複数のトランザクションからかけられる
- 占有ロック
- データを更新するためにかけるロックで、UPDATEやDELETE文の実行時にかかる
- ロックされたレコードは参照も更新もできなくなる(更新するからそれまで見ないで~)
- 同一レコードに対して1つのトランザクションからのみかけられる
- トランザクションが終了したタイミングでロックも解除される
データの整合性をどうとるかの方針:楽観ロックと悲観ロック
ロックの方式ではなく、排他制御の戦略の分類。
- 楽観ロック
- 同一レコードの同時更新は起こらないという前提に立つ
- データ更新の際ロックは行わず、データ読み取り時と更新直前の取得時とでデータに齟齬がなければ更新をかけちゃう
- 悲観ロック
- 同一レコードの同時更新が起こりうるという前提に立つ
- 更新時のデータ読み取りと同時にロックをかける
デッドロック
ロックが解放されるのがトランザクションの終了後であるとき、トランザクション内に他トランザクションのロック解放を待たなければならないような処理があると「お互いがお互いのロック解放=トランザクション終了待ち」の状態となり、トラザクションが終わらなくなる。
変換デッドロック
2つのトランザクションが共有ロックをかけているときに、両トランザクションで占有ロックをかけようとすると発生するデッドロック。
サイクルデッドロック
2つのトランザクションの中で、互いに、相手が先に占有ロックをかけた行の更新待ちの処理が発生して発生するデッドロック。
デッドロックが起こらないようにするには
- 新時は占有ロックを取得する
- 更新順序を一定にする
例えば、以下の処理ではデッドロックが発生しうる(AとBの処理はそれぞれ1トランザクション内の処理)
mysql(A)> select * from numbers where id = 1 for update;
mysql(B)> select * from numbers where id = 2 for update;
mysql(A)> select * from numbers where id = 2 for update;
-- id=1・2の処理終了を互いに待ち続け、サイクルデッドロック発生
mysql(B)> select * from numbers where id = 1 for update;
が、こうすると発生しなくなる
mysql(A)> select * from numbers where id = 1 for update;
-- BがA待ち状態に入る
mysql(B)> select * from numbers where id = 1 for update;
mysql(A)> select * from numbers where id = 2 for update;
mysql(A)> commit;
-- Aの処理完了 --
-- Bが後続の処理を流せるようになる
mysql(B)> select * from numbers where id = 2 for update;
...
まとめ
- 複数のトランザクションが同時に実行される状況でデータの整合性を保証するための方針が楽観ロック - 悲観ロック
- 悲観ロックを前提に、実際にかけるロックが共有ロック - 占有ロック
- トランザクション間で互いの共有ロック/占有ロックの解除待ちになってしまう現象がデッドロック