1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ロストアップデートと呼ばれるものはDBアーキテクト上には存在しないんじゃない?説

Posted at

ロストアップデートと呼ばれるものがあるらしい

15年ほどPostgreSQLを運用していて、最近恥ずかしながら初めて「ロストアップデート」という言葉を目にしました。
へぇーMySQLはREPEATABLE READでこんな事が起こるのかぁー。大変だなあ。と他のサイトで情報を調べながらの帰り道。調べれば調べるほど嫌な予感がしてくる。

早速帰宅後試してみました。手元にあったDocker imageから

実験準備

mysql.
mysql> show variables like 'tx_isolation';
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+
1 row in set (0.02 sec)

mysql> select version();
+-----------+
| version() |
+-----------+
| 5.7.20    |
+-----------+

mysql> CREATE TABLE Posts (  
    -> id INT UNSIGNED NOT NULL PRIMARY KEY,  
    -> b INT NOT NULL     
    -> ) ENGINE INNODB;

mysql> INSERT INTO Posts VALUES(1, 100);

と設定しました。

実験開始

トランザクションA.
mysql> begin; 
Query OK, 0 rows affected (0.00 sec)

mysql> select * from Posts where id = 1;
+----+-----+
| id | b   |
+----+-----+
| 1  | 100 |
+----+-----+
トランザクションB.
mysql> begin; 
Query OK, 0 rows affected (0.00 sec)
トランザクションA.
mysql> update Posts set b = b+1 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
トランザクションB.
mysql> select * from Posts where id = 1;
+----+-----+
| id | b   |
+----+-----+
| 1  | 101 |
+----+-----+

あれ? ああ、なるほど、トランザクションAがコミットする前に一度selectしておく必要があるのか。もう一度。

トランザクションA.
mysql> update Posts set b = 100 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> begin; 
Query OK, 0 rows affected (0.00 sec)
トランザクションB.
mysql> begin; 
Query OK, 0 rows affected (0.00 sec)
mysql> select * from Posts where id = 1;
+----+-----+
| id | b   |
+----+-----+
| 1  | 100 |
+----+-----+
トランザクションA.
mysql> update Posts set b = b+1 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
トランザクションB.
mysql> select * from Posts where id = 1;
+----+-----+
| id | b   |
+----+-----+
| 1  | 100 |
+----+-----+

うんうん。

トランザクションB.
mysql> update Posts set b = b+1 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from Posts where id = 1;
+----+-----+
| id | b   |
+----+-----+
| 1  | 102 |
+----+-----+
1 row in set (0.01 sec)

mysql> commit;
Query OK, 0 rows affected (0.01 sec)

mysql> select * from Posts where id = 1;
+----+-----+
| id | b   |
+----+-----+
| 1  | 102 |
+----+-----+
1 row in set (0.01 sec)

あれれー? 嫌な予感が当たったー。
やっぱりロストアップデートなんて起こらないんだけどー??
これってもしかしてさー、、、Active Recordとかのこういったことをロストアップデートって言ってるの?

rils.console
irb(main):010:0> post = Post.find(1)
  Post Load (3.3ms)  SELECT  "posts".* FROM "posts" WHERE "posts"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]

irb(main):014:0> post.b += 1
=> 101

irb(main):015:0> post.save
   (1.8ms)  BEGIN
  Post Update (1.9ms)  UPDATE "posts" SET "updated_at" = $1, "b" = $2 WHERE "posts"."id" = $3  [["updated_at", "2019-03-26 16:18:35.708693"], ["b", 101], ["id", 1]]
   (3.5ms)  COMMIT
=> true

これってDBの設計やトランザクション隔離レベルではなくて、findからsaveの間隔があればあるほどリスク高くなるんじゃ...別トランザクションのコミットが失われたってニュアンスはなんだかなあ...もやっと。
それをSELECT FOR UPDATEで防止するってのもなんだかなあ...もやっと。
DBのレイヤでの解決は、update table set column = column + 1で良いと思ったのですが、、、

確かにActive Recordでcolumn = column + 1ってSQLの書き方は調べても分からなかったですが、、、
が、aplicationやlibraryの都合で必要のないロックを取りましょう!ってのは個人的にはよくないと思います。こうゆうのは優秀なruby界隈の人がシュババっと実装してくれそうですし。

ロックについてはこの増田の真似をしないように。
https://anond.hatelabo.jp/20190324181740

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?