可重复读取隔离级别SELECT VS UPDATE...WHERE

也许你可以在这里为我解释一些事情:

DB=MySQL 5.7

存储引擎:InnoDB

隔离级别:可重复读取

下表:

---------------
|   MyTable   |
---------------
| PK | Concur |
---------------
| 3  |   2    |
---------------

此时我没有正在进行的事务,我选择此记录,如下所示

SELECT * FROM MyTable WHERE PK = 3

并将结果存储在我的程序中。

我现在启动一个数据库事务。 事务开始后,外部进程将PK=3的记录的Concur从2递增到3。

我尚未再次从事务内的该表中读取。

我从我的事务内部发出以下查询:

UPDATE MyTable SET Concur = 3 WHERE PK = 3 AND Concur = 2

这将通过0 records affected成功。很明显,它会根据我的事务开始后更改的数据进行评估。 仍在事务中,我随后查询:

SELECT * FROM MyTable WHERE PK = 3

它将向我返回PK = 3 and Concur = 2的记录,这些值是事务之前的值。

为什么SELECTUPDATE ... WHERE的行为不同,我缺少什么?

我认为UPDATE ... WHERE语句要么直接失败,而不是在0条记录受到影响的情况下成功,要么在那里成功,但有1条记录受到影响,然后在随后的COMMIT处失败,但不是这种混合和匹配。

这里有什么见解吗?


解决方案

https://dev.mysql.com/doc/refman/8.0/en/innodb-consistent-read.html

一致读取意味着InnoDB使用多版本控制向查询呈现某个时间点的数据库快照。查询会看到在该时间点之前提交的事务所做的更改,而不会看到后来或未提交的事务所做的更改。此规则的例外情况是,查询会看到同一事务中较早的语句所做的更改。此异常会导致以下异常:如果更新表中的某些行,SELECT会看到更新的行的最新版本,但也可能会看到任何行的较旧版本。如果其他会话同时更新同一个表,则异常情况意味着您可能会看到该表处于数据库中不存在的状态。

重要的条件是,如果您更改了行,您的一致读取将被"刷新",因此它包括您刚刚所做的更改。

但如果您更新,它始终是行的最新版本,而不是事务的一致读取可以查看的版本。因此,如果另一个事务已经进行了该更改,则您的更新可能不会产生任何净效果。这就是您观察到的情况。

因此,您的事务发出了更新,但未更改行。

这可能不是您希望InnoDB的行为方式,但它确实是这样的。

相关文章