一、定义

在《高性能MySQL》一书中,是这样描述的

mysql事务重复读取失败怎么回事 mysql 可重复读 场景_mysql


根据书上的描述,MySQL的可重复读隔离级别通过MVCC机制,解决了幻读的问题,所以不会造成幻读

二、测试

本文默认你已经知道以下的命令

1. 查询当前会话隔离级别

SELECT @@tx_isolation;

2. 设置当前会话隔离级别

set SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

3. 查询正在进行中的事务

SELECT * FROM information_schema.INNODB_TRX

准备实验的表和数据

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(30) DEFAULT NULL,
  `age` tinyint(4) DEFAULT NULL,
  PRIMARY KEY (`id`)
);
INSERT INTO user VALUES (1, '张三', 1);

测试表数据如下:

mysql事务重复读取失败怎么回事 mysql 可重复读 场景_java_02


打开两个会话窗口,分别都设置隔离级别为RR

按照顺序执行以下操作

事务A

事务B

START TRANSACTION


SELECT * FROM user



START TRANSACTION


INSERT INTO user values(2,'李四',1)


commit

SELECT * FROM user


update user set age = 2


SELECT * FROM user


按照上图序号标注的顺序,从①到⑥,在⑥查询的时候,发现确实是没有查到B事务新增的数据的,所以确实没有幻读。但是再执行⑦、⑧,会发现在⑧的时候,出现了幻读

三、分析

这里默认你已经知道了MVCC的原理,可以参考这篇文章MYSQL MVCC实现原理

简单来说,就是MySQL会默认给表增加两个字段,一个是标记创建时间,一个标记过期时间(也是删除时间),这里的时间指的是当前事务的id

SELECT
InnoDB会根据以下两个条件检查每行记录:

  • InnoDB只查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。
  • 行的删除版本要么未定义,要么大于当前事务版本号。这可以确保事务读取到的行,在事务开始之前未被删除。

只有符合上述两个条件的记录,才能返回作为查询结果

INSERT
InnoDB为新插入的每一行保存当前系统版本号作为行版本号。

DELETE
InnoDB为删除的每一行保存当前系统版本号作为行删除标识。

UPDATE
InnoDB为插入一行新记录,保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为行删除标识。

带上这两个字段,分析下测试数据

执行完②

id

name

age

创建时间

删除时间

1

张三

1

1

执行完⑤

id

name

age

创建时间

删除时间

1

张三

1

1

2

李四

1

2

执行⑥的时候,根据SELECT的时候版本号规则,查询小于等于当前版本号,所以只能查出id = 1的数据,没有幻读

执行完⑦

id

name

age

创建时间

删除时间

1

张三

1

1

2

李四

1

2

1

2

李四

1

1

根据UPDATE的版本号规则,现在数据库应该有三条数据

所以再执行⑧的查询,根据SELECT的查询规则,应该是能看到新增的数据,产生了幻读

四、总结

所以我认为MySQL的MVCC机制并没有完全解决幻读问题。

这是我自己的理解,如果有误还请指出