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

Repeatable Read isolation level SELECT vs UPDATE...WHERE

也许你可以在这里为我阐明一些事情:

DB = MySQL 5.7

存储引擎:InnoDB

隔离级别:重复table读取

以下 table:

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

此时我没有进行任何交易,我 select 这条记录就像

SELECT * FROM MyTable WHERE PK = 3

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

我现在开始一个数据库事务。 在我的交易开始后,外部进程将 Concur 用于 PK = 3 的记录从 2 增加到 3。

我还没有从我的交易中的 table 再次阅读。

我从我的交易中发出以下查询:

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

0 records affected 这将成功。很明显,它根据我的交易开始后已更改的数据进行评估。 还在交易中我随后查询:

SELECT * FROM MyTable WHERE PK = 3

这将 return 我的记录 PK = 3 and Concur = 2 这是交易前的值。

为什么 SELECTUPDATE ... WHERE 表现不同,我错过了什么?

我本以为 UPDATE ... WHERE 语句要么直接失败而不是成功,影响 0 条记录,要么它在那里成功,影响 1 条记录,然后在 COMMIT 之后失败,但是不是这种混搭。

这里有什么见解吗?

要受到可重复读取隔离级别的影响,您必须在同一个事务中。这意味着您的两个 selects 必须在事务内,因此它们不会受到您从外部事务更改数据库的任何地方的影响。

所以,正如您所说,此时我没有进行任何交易,我 select 这条记录就像 SELECT * FROM MyTable WHERE PK = 3 一样,并将结果存储在我的程序中。 您只用一个语句进行交易。 之后,您开始使用您的更新进行交易。

你应该做的是

 START TRANSACTION
      SELECT * FROM MyTable WHERE PK = 3
      ------                                        START TRANSACTION
      -----                                             UPDATE MyTable SET Concur = 3 WHERE PK = 3 AND Concur = 2                                            
      -------                                       END TRANSACTTION
      -------
      -------
      SELECT * FROM MyTable WHERE PK = 3
 END TRANSACTION

左侧是 select 交易,右侧是更新。这样左边的交易就不会受到更新的影响了。

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

A consistent read means that InnoDB uses multi-versioning to present to a query a snapshot of the database at a point in time. The query sees the changes made by transactions that committed before that point of time, and no changes made by later or uncommitted transactions. The exception to this rule is that the query sees the changes made by earlier statements within the same transaction. This exception causes the following anomaly: If you update some rows in a table, a SELECT sees the latest version of the updated rows, but it might also see older versions of any rows. If other sessions simultaneously update the same table, the anomaly means that you might see the table in a state that never existed in the database.

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

但是,如果您进行更新,则更新始终是该行的最新版本,而不是您的事务的一致读取可以查看的版本。因此,如果另一笔交易已经进行了更改,您的更新可能没有任何实际效果。这就是你观察到的情况。

因此您的事务发出了更新但没有更改该行。

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

FOR UPDATE 表达你的意图:

BEGIN;
SELECT ... FOR UPDATE;
...
(no other thread can change that row until you `COMMIT` or `ROLLBACK`)
...
COMMIT;

另一方面...

BEGIN;
...
(At this point another thread modifies the row...)
...
SELECT ... FOR UPDATE;  -- you are blocked until they COMMIT or ROLLBACK