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)