ロックがわからない。MySQL InnoDBの行レベルロックを読んだけど、イマイチわからない。というわけで、社内の勉強会で知ったことをまとめてみる。
FOR UPDATEかLOCK IN SHARE MODEによって、そのトランザクション中に走る別画面でのクエリの処理のタイミングが異なる。
FOR UPDATEによるロック
まずは画面Aで接続が行われ、トランザクションが開始され、SELECT文が発行されたとする。
[画面A]
BEGIN; // トランザクションA開始 SELECT * FROM users WHERE id = 1 FOR UPDATE; COMMIT; // トランザクションA終了
そしてトランザクションAの開始後〜終了前に画面B〜Dがクエリを発行したとする。
ちなみにトランザクションを開始しておかないとFOR UPDATE
を書いても画面Aでのロックが始まらないので要注意。
[画面B]
// トランザクションAでSELECT発行後にクエリ発行 SELECT * FROM user_poarams WHERE id = 1 FOR UPDATE; // … トランザクションAのCOMMITが終わるまで返ってこない
[画面C]
// トランザクションAでSELECT発行後にクエリ発行 SELECT * FROM user_poarams WHERE id = 1 LOCK IN SHARE MODE; // … トランザクションAのCOMMITが終わるまで返ってこない
[画面D]
// トランザクションAでSELECT発行後にクエリ発行 SELECT * FROM user_poarams WHERE id = 1; // トランザクションAのCOMMITを待たずに即座に返ってくる
LOCK IN SHARE MODEによるロック
まずは画面Eで接続が行われ、トランザクションが開始され、SELECT文が発行されたとする。
[画面E]
BEGIN; // トランザクションE開始 SELECT * FROM users WHERE id = 1 FOR UPDATE; COMMIT; // トランザクションE終了
そしてトランザクションEの開始後〜終了前に画面F〜Hがクエリを発行したとする。
[画面F]
// トランザクションEのSELECT発行後にクエリ発行 SELECT * FROM users WHERE id = 1 FOR UPDATE; // … トランザクションEのCOMMITが終わるまで返ってこない
[画面G]
// トランザクションEのSELECT発行後にクエリ発行 SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE; // トランザクションEのCOMMITを待たずに即座に返ってくる
[画面H]
// トランザクションEのSELECT発行後にクエリ発行 SELECT * FROM users WHERE id = 1; // トランザクションEのCOMMITを待たずに即座に返ってくる
ひとまずまとめ
先に接続した方のロック方法 | FOR UPDATE | LOCK IN SHARE MODE | ロック無し |
---|---|---|---|
(A) FOR UPDATE | (B) 待つ | (C) 待つ | (D) 待たない |
(E) LOCK IN SHARE MODE | (F) 待つ | (G) 待たない | (H) 待たない |
フィーリングで理解してみる
FOR UPDATE
でロックをすると、ロック無しの場合を覗いてそのトランザクションが終わるまでレコードの読み取りを待たせる。つまり自分(A)以降の他のクエリ(B)(C)に対して自分のトランザクションで行われた変更を必ず反映させる。逆に、自分(B)(F)より前にロックしているクエリ(A)(E)がいたら、そのトランザクションが終わるまで待つ。つまり、前後のクエリに対して自分の処理が排他的に行われるため、最新の情報を扱うことができる。
すべてFOR UPDATE
にすれば必ず一貫性は保たれるけど、トランザクションが終わるまで待つために
処理がなかなか進まない。読み取るだけだから同時に走ってもいいよ、トランザクションも必要ないよってこともある。なのでFOR UPDATE
だけでは不十分。
一方、LOCK IN SHARE MODE
でロックすると、FOR UPDATE
によるロック以外では待たない。比較的カジュアルに取りに行く。でもFOR UPDATE
をかけた場合(A)(E)にはしっかり待つ。なので、FOR UPDATE
ほど排他的ではなく流れもスムーズだし、FOR UPDATE
による変更を待つので最新の情報を扱うことができる。
じゃあデフォルトでLOCK IN SHARE MODE
によるロックを掛けて、明示的にFOR UPDATE
を宣言したときだけ排他ロックかければいいんじゃないかと。ただこうなっていない理由は、完全に想像だけど、読み出しのみのマスターデータだったらロックなんて要らないし、むしろリードの回数が多すぎてロックしてる暇なんてないよってことがあるからじゃないかなぁ。想像。
そんなわけで、
- 「更新するから整合性のために待ってて!俺も待つし!」ってときには
FOR UPDATE
- 「自分は更新しないけど、他の人が
FOR_UPDATE
で更新するなら待つよ!」ってときにはLOCK IN SHARE MODE
- 「更新することもされることもないからガシガシ読みに行くよ!」ってときにはロック無し
という理解をしたんだけど、どうなんだろう。
追記
わかりやすい記事を見つけたのでメモしておく。