为啥不能更新已经被 MySQL 中另一个事务更新的行

Posted

技术标签:

【中文标题】为啥不能更新已经被 MySQL 中另一个事务更新的行【英文标题】:Why can not update a row that already updated by another transaction in MySQL为什么不能更新已经被 MySQL 中另一个事务更新的行 【发布时间】:2020-12-27 16:37:32 【问题描述】:

我正在尝试了解 mysql InnoDB 的可重复读取隔离级别。但是当我尝试这 2 个事务时,它的行为是我无法理解的。

这是我的测试初始化​​

mysql> SHOW CREATE TABLE `test`;
+-------+---------------------------------------------------------------------------------------+
| Table | Create Table                                                                          |
+-------+---------------------------------------------------------------------------------------+
| test  | CREATE TABLE `test` (
  `ID` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 |
+-------+---------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT * FROM `test`;
+----+
| ID |
+----+
|  1 |
+----+
1 row in set (0.00 sec)

mysql> SELECT @@TX_ISOLATION;
+-----------------+
| @@TX_ISOLATION  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)

No Tx1 Tx2
1 START TRANSACTION START TRANSACTION
2 SELECT * FROM test;# Return 1 SELECT * FROM test;# Return 1
3 SELECT * FROM test WHERE ID = 1 FOR UPDATE;# return 1
4 UPDATE test SET ID = 2 WHERE ID = 1; # OK UPDATE test SET ID = 3 WHERE ID = 1; # Lock wait
5 SELECT * FROM test;# Return 2 # Keep waiting
6 COMMIT; # OK # Query OK: rows matched:0, changed: 0, warnings: 0
7 SELECT * FROM test;# Return 2 SELECT * FROM test WHERE ID = 1;# Return 1
8 UPDATE test SET ID = 3 WHERE ID = 1; #Query OK: rows matched:0, changed: 0, warnings: 0
9 SELECT * FROM test;# Return 2 SELECT * FROM test;# Return 1 <-- cannot update 1 to 3 even the row exists
10 UPDATE test SET ID = 3 WHERE ID = 2;#Query OK: rows matched:1, changed: 1, warnings: 0
11 COMMIT; #OK
12 SELECT * FROM test# Return 3 SELECT * FROM test# Return 3

我想知道 MySQL 如何在第 8 行和第 10 行处理 Tx2 中的 ID=1 和 ID=2。 如果我在 Tx2 的第 4 行使用UPDATE test SET ID = 3 WHERE ID = 2,即使 Tx1 只持有 ID = 1 的排他锁,仍然需要锁定等待?

【问题讨论】:

ID不同时的锁等待可能是在等待间隙锁。 【参考方案1】:

在第 8 步,不再有锁定记录,因为TX1 已执行 COMMIT,这将清除锁定。

也不再有 ID=1 的记录,因为它已被更新。

在步骤 9 和 10,Tx2 无法更新记录,因为可重复读取。它无法看到 id=2 的记录,也不再找到 id=1 的记录(因为它已经消失/更改了)。

只有在 COMMIT 之后,Tx2 才能看到新的/更改的数据。

【讨论】:

但是 ID=1 仍然可以通过选择在 Tx2 中看到。如果我们可以选择它但不能更新它,那就太奇怪了。更新一个不可见的 ID 也很奇怪。 由于可重复读取,它仍然可见,请参阅 this 很好的解释为什么无法更改此数据。 (注意:读取的数据不能更改,但您数据库中的数据可以!)

以上是关于为啥不能更新已经被 MySQL 中另一个事务更新的行的主要内容,如果未能解决你的问题,请参考以下文章

使用同一表中另一列的键更新 mysql 列

根据MySQL中另一列分组的另一列的顺序更新列

使用mySQL中另一个表中的select结果更新表中的字段

在事务中使用悲观锁不能阻止记录被更新?

第十章 事务更新

为啥我不能更新 laravel 中的字段?