我有下表:
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 文档说关于可重复读取:
这是 InnoDB 的默认隔离级别。同一事务中的一致性读取读取第一次读取建立的快照。这意味着,如果您在同一事务中发出多个普通(非锁定)SELECT 语句,这些 SELECT 语句也相互一致。请参见第 15.7.2.3 节,“一致的非锁定读取”。
那么我在这里错过了什么?为什么事务中的 SELECT 语句似乎并非全部使用由第一个 SELECT 语句建立的快照?