TL;DR
- トランザクション分離レベルが
REPEATABLE-READの場合、SELECT .. FOR UPDATEやSELECT .. LOCK IN SHARE MODEとロックなしのSELECTの結果が違うことがある。 - 仕様らしい => 漢(オトコ)のコンピュータ道: InnoDBのREPEATABLE READにおけるLocking Readについての注意点
ドキュメント探してみたけどほんのちょっとだけしか書いてないような気がする。
SELECT … LOCK IN SHARE MODE は、これらの行のいずれかがコミットされていない別のトランザクションによって変更された場合、クエリーはそのトランザクションが終了するまで待機してから、最新の値を使用します。
しゃらっと 最新の値を使用します と書いてあるけど、最新の値が意味するのは「最後にコミットされた時の値」であって、
REPEATABLE-READのはずの分離レベル内でREAD-COMMITTEDっぽい動作が見える。
どういうことかというと
mysql57 4> START TRANSACTION;
mysql57 5> START TRANSACTION;
mysql57 4> SELECT * FROM t1;
+-----+--------+
| num | val |
+-----+--------+
| 1 | before |
+-----+--------+
1 row in set (0.00 sec)
mysql57 5> SELECT * FROM t1;
+-----+--------+
| num | val |
+-----+--------+
| 1 | before |
+-----+--------+
1 row in set (0.00 sec)
mysql57 4> UPDATE t1 SET val = 'after' WHERE num = 1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql57 4> COMMIT AND CHAIN;
Query OK, 0 rows affected (0.00 sec)
mysql57 4> SELECT * FROM t1;
+-----+-------+
| num | val |
+-----+-------+
| 1 | after |
+-----+-------+
1 row in set (0.00 sec)
ほぼ同時に開始したトランザクション
4と5がいて、4はある行の値をアップデートしてコミットした。
この時に”after”が見えるのがREAD-COMMITTED、”before”が見えるのがREPEATABLE-READのはずで確かにそうなるんだけど、ブロッキングリード(SELECT .. FOR UPDATE, SELECT .. LOCK IN SHARE MODE)の場合はmysql57 5> SELECT * FROM t1 FOR UPDATE;
+-----+-------+
| num | val |
+-----+-------+
| 1 | after |
+-----+-------+
1 row in set (0.00 sec)
コミット済みの値が見える。
同じトランザクションの中で非ブロッキングリードとブロッキングリードをすると
mysql57 5> SELECT * FROM t1;
+-----+--------+
| num | val |
+-----+--------+
| 1 | before |
+-----+--------+
1 row in set (0.00 sec)
mysql57 5> SELECT * FROM t1 FOR UPDATE;
+-----+-------+
| num | val |
+-----+-------+
| 1 | after |
+-----+-------+
1 row in set (0.00 sec)
mysql57 5> SELECT * FROM t1;
+-----+--------+
| num | val |
+-----+--------+
| 1 | before |
+-----+--------+
1 row in set (0.00 sec)
返ってくる値が変わる。
この話自体は有名な気がしていたんだけれど、ドキュメントには小さくしか見つけられなかったのでひょっとしたら有名じゃないのかなって。
(これ実演してみせたら爆笑されたw)