GA

2017/05/26

MySQLのSELECT .. FOR UPDATEはREPEATABLE-READでも直近にコミットされたレコードを返す

TL;DR


ドキュメント探してみたけどほんのちょっとだけしか書いてないような気がする。
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)
ほぼ同時に開始したトランザクション45がいて、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)

1 件のコメント :

  1. 同じ問題で悩んでいて、記事にしていただいていたので助かりました🙇‍♂️

    返信削除