一、定义
在《高性能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);
测试表数据如下:
打开两个会话窗口,分别都设置隔离级别为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
机制并没有完全解决幻读问题。
这是我自己的理解,如果有误还请指出