MVCC 如何与 Lock in MySql 一起工作?

How MVCC works with Lock in MySql?

我知道使用Mysql中的锁或者MVCC可以实现并发控制,比如repeatable-reading。 但是我不知道MVCC是如何避免幻读的。在其他地方了解到一般是通过MVCC和Gap-Lock来实现的,但是我目前的理解是MVCC不需要锁,即更新和删除都是使用undo-logs来实现的。 如果是,MVCC和锁机制是如何协同工作的?

例如,为了避免幻读,MVCC 会在 T1 中的某些行上添加间隙锁吗? 如果是这样,MVCC如何在T2发生更新时,一般只是追加一个更新undo-log?还是阻止它?

MySQL(特别是 InnoDB)不支持 REPEATABLE-READ 锁定语句。例如,UPDATEDELETESELECT...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 |
+----+------+

到目前为止,还不错。现在打开第二个 window 并更新值:

mysql> update mytable set x = 84;
Query OK, 1 row affected (0.03 sec)
Rows matched: 1  Changed: 1  Warnings: 0

现在回到第一个 window,由于 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)

您可以根据需要来回多次,同一事务可以 return 这两个值,具体取决于锁定读取与非锁定读取。

这是 InnoDB 的一个奇怪行为,但它允许读取不被阻塞。我使用了其他 MVCC 实现,例如 InterBase/Firebird,它们以不同的方式解决了这个问题。它将阻止读取,直到第二个 window 中的事务提交或回滚。如果它回滚,那么锁定读取可以读取原始值。如果其他事务提交,则锁定读取会出错。

InnoDB 在如何实现 MVCC 上做出了不同的选择,以避免阻塞读取。但它会导致奇怪的行为,即锁定读取必须查看最新提交的行版本。

正如歌曲所说,“你不能总是得到你想要的。”