了解 InnoDB 可重复读取隔离级别快照

Understanding InnoDB Repeatable Read isolation level snapshots

我有以下 table:

CREATE TABLE `accounts` (
  `name` varchar(50) NOT NULL,
  `balance` int NOT NULL,
  PRIMARY KEY (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

它有两个帐户。 “Bob”的余额为 100。“Jim”的余额为 200。

我运行此查询将 50 从 Jim 转移给 Bob:

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;

SELECT * FROM accounts;

SELECT SLEEP(10);

SET @bobBalance = (SELECT balance FROM accounts WHERE name = 'bob' FOR UPDATE);
SET @jimBalance = (SELECT balance FROM accounts WHERE name = 'jim' FOR UPDATE);
UPDATE accounts SET balance = @bobBalance + 50 WHERE name = 'bob';
UPDATE accounts SET balance = @jimBalance - 50 WHERE name = 'jim';

COMMIT;

当该查询正在休眠时,我 运行 在不同的会话中执行以下查询以将 Jim 的余额设置为 500:

UPDATE accounts SET balance = 500 WHERE name = 'jim';

以为会发生的是这会导致错误。该事务会将 Jim 的余额设置为 150,因为事务中的第一次读取(在 SLEEP 之前)将建立一个快照,其中 Jim 的余额为 200,并且该快照将在以后的查询中用于获取 Jim 的余额。所以我们将从 200 中减去 50,即使 Jim 的余额实际上已被其他查询更改为 500。

但事实并非如此。其实最后的结果是对的。 Bob 有 150,Jim 有 450。但我不明白这是为什么。

MySQL 文档介绍了 Repeatable 阅读:

This is the default isolation level for InnoDB. Consistent reads within the same transaction read the snapshot established by the first read. This means that if you issue several plain (nonlocking) SELECT statements within the same transaction, these SELECT statements are consistent also with respect to each other. See Section 15.7.2.3, “Consistent Nonlocking Reads”.

那么我在这里缺少什么?为什么交易中的 SELECT 语句似乎没有全部使用第一个 SELECT 语句建立的快照?

repeatable-read 行为仅适用于 non-locking SELECT 查询。它从事务中第一个查询建立的快照中读取。

但是任何锁定 SELECT 查询都会读取该行的最新提交版本,就好像您是在 READ-COMMITTED 隔离级别开始您的事务一样。

如果 SELECT 涉及任何类型的修改数据的 SQL 语句,则它是隐含的锁定读取。

例如:

INSERT INTO table2 SELECT * FROM table1 WHERE ...;

以上锁定了 table1 中检查过的行,即使该语句只是将它们复制到 table2。

SET @myvar = (SELECT ... FROM table1 WHERE ...);

这也是将 table1 中的一个值复制到一个变量中。它将检查的行锁定在 table1.

同样在触发器中调用的 SELECT 语句,或作为 multi-table UPDATE 或 DELETE 的一部分,等等。任何时候 SELECT 是修改任何数据的更大语句的一部分(在 table 或变量中),它会锁定 SELECT.

检查的行

因此它是一个锁定读取,并且就其读取的行版本而言,其行为类似于更新。