0 / 3
ロックの互換性(Lock Compatibility)
共有・共有
すでにかかっているロックと新しく要求したロックを一緒にかけられれば、後のトランザクションはすぐに実行され、一緒にかけられなければ待つ。
どちらも読むだけのとき、共有ロック同士が一緒にかかるかを見る。
1 / 3
T1が共有ロックをかけて読む
T1がXに共有ロックをかけて10を読む。
2 / 3
T2もすぐ読む
T2もXに共有ロックをかける。共有ロック同士は一緒にかけられるので、T2は待たない。
3 / 3
結果:共有ロック同士は一緒にかかる
読み同士は値を変えないので、共有ロックは一緒にかけてもよい。
1 / 5
T1が共有ロックをかけて読む
T1がXに共有ロックをかけて10を読む。
2 / 5
T2は待つ
T2は書くために排他ロックを要求する。XにはT1の共有ロックがかかっていて、一緒にかけられない。
3 / 5
T1がロックを解除する
T1が読み終えてXのロックを解除する。待っていたT2がこれでロックをかけられる。
4 / 5
T2が今度は書く
T2がXに排他ロックをかけて9を書く。
5 / 5
結果:一緒にかけられない
T1が読んでいる途中で値が変わらないように、T2はT1がロックを解除するまで待つ。
1 / 5
T1が排他ロックをかけて書く
T1がXに排他ロックをかけて9を書く。
2 / 5
T2は待つ
T2は読むために共有ロックを要求する。XにはT1の排他ロックがかかっているので、読むことも止められる。
3 / 5
T1が終わる
T1がCommitしてXのロックを解除する。待っていたT2がこれでロックをかけられる。
4 / 5
T2が今度は読む
T2がXに共有ロックをかけ、確定した9を読む。
5 / 5
結果:一緒にかけられない
排他ロックは、書いている途中の値を他のトランザクションが読めないようにする。
1 / 5
T1が排他ロックをかけて書く
T1がXに排他ロックをかけて9を書く。
2 / 5
T2は待つ
T2も書くために排他ロックを要求するが、T1の排他ロックのために待つ。
3 / 5
T1が終わる
T1がCommitしてXのロックを解除する。待っていたT2がこれでロックをかけられる。
4 / 5
T2が今度は書く
T2がXに排他ロックをかけ、9から1を引いて8を書く。
5 / 5
結果:一緒にかけられない
二つの書き込みが混ざらず、一つずつ順に実行される。
まとめ:一緒にかけられるのは共有ロック同士だけで、排他ロックが一つでもあれば後のトランザクションは待つ。
1 / 5
T1がテーブル全体にロックをかける
T1が行1を更新するために、注文テーブル全体に排他ロックをかける。
2 / 5
T2は待つ
T2は別の行である3番の注文を更新しようとする。しかしテーブル全体にロックがかかっているので待つ。
3 / 5
T1が終わる
T1がCommitしてテーブルのロックを解除する。
4 / 5
T2が今度は更新する
T2がテーブルに排他ロックをかけて行3を更新する。
5 / 5
結果:並行性が低い
T1とT2は別々の行を更新したのに、順番に実行された。
1 / 5
T1が行1にだけロックをかける
T1が更新する行1にだけ排他ロックをかける。
2 / 5
T2もすぐ更新する
T2が行3に排他ロックをかける。T1のロックは行1にだけかかっているので、T2は待たない。
3 / 5
T1が終わる
T1がCommitして行1のロックを解除する。
4 / 5
T2が終わる
T2がCommitして行3のロックを解除する。
5 / 5
結果:並行性が高い
別々の行を更新するトランザクションは、同時に実行された。
代償:行ごとにロックをかけるので、管理するロックの数が増える。参考:行ロックが多くなりすぎると、それをテーブルロック一つに置き換えるDBMSもある(SQL Serverなど)。これをロックエスカレーション(Lock Escalation)という。OracleやMySQL(InnoDB)のように、エスカレーションしないDBMSもある。
1 / 6
T1がAを読む
T1がAに共有ロックをかけて100を読む。
2 / 6
T1がAのロックを解除する
T1はこの後、Bにもロックをかけなければならない。ところがここでAのロックを先に解除したので、Bにロックをかけた瞬間に2PLの規則を破ることになる。
3 / 6
T2が割り込む
Aのロックが解除されたので、T2がAに排他ロックをかけて50を引く。
4 / 6
T2が振替を終える
T2がBに排他ロックをかけて50を足し、ロックをすべて解除して終わる。
5 / 6
T1がBを読む
T1がBに共有ロックをかけて読む。振替がすでに終わっているので150だ。ロックを解除した後に新しいロックをかけたので、2PLを破った。
6 / 6
結果:ロックをかけても合計が間違う
T1がAのロックを早く解除しすぎて、その隙にT2の振替が割り込んだ。
1 / 7
成長相:Aにロックをかける
T1がAに共有ロックをかけて100を読む。まだBにロックをかけていないので、Aのロックは解除しない。
2 / 7
T2は待つ
T2がAに排他ロックを要求するが、T1の共有ロックのために待つ。ロックを早めに解除する方式では、ここでT2が割り込んだ。
3 / 7
成長相:Bにロックをかける
T1がBにも共有ロックをかけて100を読む。これで必要なロックをすべてかけた。
4 / 7
縮退相:ロックを解除する
T1が合計200を求めてロックを解除する。ここからT1は新しいロックをかけられない。
5 / 7
T2が今度は振替をする
待っていたT2がAに排他ロックをかけて50を引く。
6 / 7
T2が振替を終える
T2がBに排他ロックをかけて50を足す。
7 / 7
結果:直列化可能だ
すべてのトランザクションが2PLを守れば、交互に実行しても一つずつ順に実行したのと結果が同じになる。これを直列化可能という。
成長相:T1はRead(B)までロックをかけるだけだ。縮退相:T1はunlock(A, B)からロックを解除するだけだ。
1 / 5
T1が9を書く
T1がXに排他ロックをかけて9を書く。まだCommitしていない。
2 / 5
T1がCommit前にロックを解除する
T1はもうかけるロックがないので、2PLの規則ではXのロックを解除してよい。しかし、まだCommitしていない。
3 / 5
T2が途中の値を読む
T2がXに共有ロックをかけ、まだ確定していない9を読む。
4 / 5
T1が取り消す
決済が失敗してT1がRollbackし、Xは10に戻る。T2が読んだ9は無効になる。
5 / 5
結果:基本の2PLは連鎖ロールバックを防げない
基本の2PLが決めるのは、ロックを一つでも解除した後は新しいロックをかけないという順序だけだ。ロックをCommitの前に解除するか後に解除するかは決めない。
1 / 5
T1が9を書く
T1がXに排他ロックをかけて9を書く。CommitかRollbackをするまで、このロックを解除しない。
2 / 5
T2は待つ
T2がXを読もうとするが、T1の排他ロックのために待つ。基本の2PLでは、ここでT2が9を読んだ。
3 / 5
T1が取り消してロックを解除する
T1がRollbackしてXを10に戻してから、ようやくロックを解除する。
4 / 5
T2が確定した値を読む
待っていたT2がXに共有ロックをかけ、確定した10を読む。
5 / 5
結果:厳密2PLは連鎖ロールバックを防ぐ
T2は確定した値だけを読んだので、T1のRollbackとは関係ない。
厳格2PL:Rigorous 2PLともいう。共有ロックまで含めたすべてのロックを、CommitかRollbackをするまで解除しない。連鎖ロールバックを防ぐ点は厳密2PLと同じで、Commitした順に一つずつ実行したのと結果が同じになる。実際のDBMSはほとんどがこの方式を使う。
1 / 5
T1がAにロックをかける
T1がAに排他ロックをかけてAを更新する。
2 / 5
T2がBにロックをかける
T2がBに排他ロックをかけてBを更新する。
3 / 5
T1がT2を待つ
T1がBを更新しようとするが、T2の排他ロックのために待つ。T1はAにかけたロックを解除せずに待つ。
4 / 5
T2がT1を待つ
T2もAを更新しようとするが、T1の排他ロックのために待つ。これで二つとも、相手がロックを解除するのを待つだけになる。
5 / 5
結果:デッドロック
DBMSが手を打たない限り、二つのトランザクションはどちらもロックを解除しないので、いつまでも待つ。