データ指向アプリケーションデザインの「7.2.3.4 compare-and-set」の最後に以下の記述があった。

データベースのcompare-and-set操作を頼りにする前に、それが安全なものかを調べてください。

普段使っているMySQL(InnoDB)で更新のロストが防げるか調べてみた。

結論

防げる。

検証

検証に使用したMySQLは5.7系の公式Dockerイメージ。

初期データは本のサンプルに合わせてこんな感じ。

CREATE TABLE wiki_pages
(
    id      INT UNSIGNED NOT NULL PRIMARY KEY,
    content TEXT
) ENGINE INNODB;

INSERT INTO wiki_pages VALUES (1234, 'old content');

まず1つ目のトランザクションで以下のクエリまで実行する。スナップショットからの読み取り。

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT id, content FROM wiki_pages WHERE id = 1234;
+------+-------------+
| id   | content     |
+------+-------------+
| 1234 | old content |
+------+-------------+
1 row in set (0.01 sec)

ここで別のトランザクションを開始し、以下のクエリを実行する。先程のトランザクションで読み取ったレコードを更新する。

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT id, content FROM wiki_pages WHERE id = 1234;
+------+-------------+
| id   | content     |
+------+-------------+
| 1234 | old content |
+------+-------------+
1 row in set (0.00 sec)

mysql> UPDATE wiki_pages SET content = 'new content'
    -> WHERE id = 1234 AND content = 'old content';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)

また最初のトランザクションに戻り以下のクエリを実行。これがcompare-and-set。

mysql> UPDATE wiki_pages SET content = 'new content'
    -> WHERE id = 1234 AND content = 'old content';
Query OK, 0 rows affected (8.44 sec)

更新が空振り、compare-and-setの狙い通りトランザクション開始後に行われた変更を上書きすることがない。

データ指向アプリケーションデザイン以外の参考

漢(オトコ)のコンピュータ道: InnoDB の REPEATABLE READ における Locking Read についての注意点の以下の一文がすべてを語っている。

Locking ReadはREAD COMMITTED相当の挙動になると考えて貰えれば差し支えない。