[データベース] MySQLのInnoDBにおける行ロック:トランザクション――共有モードと排他モード、デッドロックの問題
はじめに
大前提として、現在実行中のトランザクションにおける「分離レベル」(ISOLATION LEVEL)によって挙動が変わってくる。
特に、INSERTを認めるか否かで大きく異なる。
UPDATE、DELETEの場合
特に指定しなくても、基本的に以下の「排他モード」で行ロックが行われる。
ロックのモード
共有モードと排他モードの二つがあるが、いずれも以下の特徴がある。
- 対象となる最新の有効なすべての行を読みとり、それをロックする
- その対象の行(最新データのレコード)が他の実行中トランザクションにある場合(つまり、そのトランザクションで対象の行が変更された場合)、それがコミットされるまで待つ
- ノーマルなSELECTは、どのモードでロックされていても即実行が可能
共有モード
別トランザクションにおいて、共有モードでロックされた行に対して――
別トランザクションでロックされた行に対して即実行が可能
SELECT
SELECT … LOCK IN SHARE MODE
別トランザクションでロックされた行に対して即実行は不可(コミットを待つ)
SELECT … FOR UPDATE
UPDATE
DELETE
排他モード
別トランザクションでロックされた行に対して即実行が可能
SELECT
別トランザクションでロックされた行に対して即実行は不可(コミットを待つ)
SELECT … LOCK IN SHARE MODE
SELECT … FOR UPDATE
UPDATE
DELETE
SELECTの場合
SELECTロックの目的
基本的に、「最新のデータ(レコード)を確実に読み込むため」に行う(特に共有モードの場合)。
かならずしも、ロックをすること自体が目的ではない。
共有モード
SELECT … LOCK IN SHARE MODE
SELECT sample_column FROM sample_table WHERE id = 1 LOCK IN SHARE MODE;
排他モード
SELECT … FOR UPDATE
SELECT sample_column FROM sample_table WHERE id = 1 FOR UPDATE;
ロックの注意点:デッドロックが起きやすい問題
共有モードでは、他のトランザクションによるSELECT … LOCK IN SHARE MODEが許可されているため、複数のトランザクションが同一の行に対して共有モードでロックができる。
結果として、双方がUPDATEなど共有モードでも許可されていない処理を実行しようとすると、双方がロックの解除を待つ状態に陥ることになる。
これを「デッドロック」という。
例
(同一の行に対して)
- トランザクションA:SELECT … LOCK IN SHARE MODE
- トランザクションB:SELECT … LOCK IN SHARE MODE
- トランザクションA:UPDATE
- トランザクションBがロックしているので待ち状態に
- トランザクションB:UPDATE
- トランザクションAがロックしているので待ち状態に
=>どちらも待ち状態になる
安易に共有モードのロックを使うと、こういう問題が起きやすいので要注意。処理のオーバーヘッドが多少生じてしまうが、排他モードを利用したほうがいいだろう。
ロックの解除
該当トランザクションをコミットかロールバックした場合に、自動的にロックは解除される。
逆をいえば、それまではロックされつづける。
まとめ
トランザクションの分離レベルのことも含めて考えるとややこしすぎるが、行ロックが必要な場合は基本的に、SELECTでは「FOR UPDATE」を使っておけば間違いはない。