MySQL(特别是 InnoDB)不支持 REPEATABLE-READ 锁定语句。例如,UPDATE
, DELETE
or SELECT...FOR UPDATE
。这些语句始终锁定最近提交的行版本,就好像事务隔离级别是 READ-COMMITTED。
你可以观察到这种情况的发生:
mysql> create table mytable (id int primary key, x int);
Query OK, 0 rows affected (0.05 sec)
mysql> insert into mytable values (1, 42);
Query OK, 1 row affected (0.02 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from mytable;
+----+------+
| id | x |
+----+------+
| 1 | 42 |
+----+------+
到目前为止,一切都很好。现在打开第二个窗口并更新值:
mysql> update mytable set x = 84;
Query OK, 1 row affected (0.03 sec)
Rows matched: 1 Changed: 1 Warnings: 0
现在回到第一个窗口,由于 REPEATABLE-READ,非锁定读取仍会查看原始值,但锁定读取会查看最近提交的版本:
mysql> select * from mytable;
+----+------+
| id | x |
+----+------+
| 1 | 42 |
+----+------+
1 row in set (0.00 sec)
mysql> select * from mytable for update;
+----+------+
| id | x |
+----+------+
| 1 | 84 |
+----+------+
1 row in set (0.00 sec)
mysql> select * from mytable;
+----+------+
| id | x |
+----+------+
| 1 | 42 |
+----+------+
1 row in set (0.00 sec)
您可以根据需要来回任意多次,并且同一事务可以返回两个值,具体取决于执行锁定读取还是非锁定读取。
这是 InnoDB 的一个奇怪的行为,但它允许读取不被阻塞。我使用过其他 MVCC 实现,例如 InterBase/Firebird,它们以不同的方式解决了这个问题。它将阻止读取,直到第二个窗口中的事务提交或回滚。如果回滚,那么加锁读就可以读到原来的值。如果其他事务提交,则锁定读取会出错。
InnoDB 在如何实现 MVCC 上做出了不同的选择,以避免阻塞读取。但它会导致奇怪的行为,即锁定读取必须查看最新提交的行版本。
正如歌曲所说,“你不可能总是得到你想要的。”