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

问题描述

我有下表:

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

其中有两个帐户。 “鲍勃”的余额为100。“吉姆”的余额为200。

我运行此查询将50从吉姆转移到鲍勃:

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的余额。因此,即使Jim的余额实际上已被其他查询更改为500,我们也会从200中减去50。

但事实并非如此。实际上,最终结果是正确的。鲍勃有150,吉姆有450。但是我不明白为什么会这样。

MySQL文档介绍了有关“可重复读取”的内容:

这是InnoDB的默认隔离级别。同一事务中的一致读取读取由第一次读取建立的快照。这意味着,如果您在同一事务中发出多个普通(非锁定)SELECT语句,则这些SELECT语句彼此之间也是一致的。请参见第15.7.2.3节“一致的非锁定读取”。

那么我在这里想念什么?为什么看起来事务中的SELECT语句没有全部使用第一个SELECT语句建立的快照?

解决方法

可重复读取行为仅适用于非锁定SELECT 查询。它从事务中第一个查询建立的快照中读取。

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

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

例如:

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

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

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

这还将表1中的值复制到变量中。它将表1中已检查的行锁定。

与SELECT语句类似,这些语句在触发器中或作为多表UPDATE或DELETE的一部分等被调用。只要SELECT是修改任何数据(在表或变量中)的较大语句的一部分,它就会锁定SELECT检查的行。

因此,它是锁定读取,并且在读取哪个行版本时,其行为类似于UPDATE。

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...