MySQL 的 Innodb 在 REPEATABLE-READ 的事务隔离级别下,幻读问题记录
程序员文章站
2022-06-14 16:37:06
...
看过一些博客和文章,有的说 MySQL Innodb RR 级别下,解决了幻读问题,也有的说没有解决,也还有一些说解决了部分幻读问题,但是都没有想过实验去论证,据着严谨的我,特此实验一番记录一下
实验前提:
1.使用 SELECT @@tx_isolation
查询当前的事务隔离级别,确保是 REPEATABLE-READ
2.执行以下表结构语句创建表
CREATE TABLE `t_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(20) NOT NULL,
`pwd` varchar(20) NOT NULL,
`is_deleted` tinyint(4) NOT NULL,
`gmt_created` datetime NOT NULL,
`gmt_modified` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `t_user_username_uindex` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
3.这里需要了解快照读和当前读两个概念。
幻读场景1
序号 | 事务A | 事务B | 现象 | 当前读/快照读 |
---|---|---|---|---|
1 | 开始事务(start transaction) | 开始事务(start transaction) | 无异常 | |
2 | 查询表所有数据(select * from t_user) | 事务A执行结果为空 | 快照读 | |
3 | 插入一条数据(insert into t_user(id, username, pwd, is_deleted, gmt_created, gmt_modified) values(1, ‘test’, ‘test’, 0, now(), now())) | 事务B执行成功 | 当前读 | |
4 | 提交事务(commit) | 事务B事务提交成功 | ||
5 | 再次查询表所有数据(select * from t_user) | 事务A执行结果和第2步的一致 | 快照读 | |
6 | 再次查询表所有数据(select * from t_user for update) | 事务A执行结果和第2步的不一致 | 当前读 | |
7 | 提交事务(commit) |
有人会说这种场景本身就不合理,可以接着看场景2
幻读场景2
序号 | 事务A | 事务B | 现象 | 当前读/快照读 |
---|---|---|---|---|
0 | 在 username 字段建立唯一索引 |
|||
1 | 开始事务(start transaction) | 开始事务(start transaction) | 无异常 | |
2 | 查询表所有数据(select * from t_user where username = ‘aaa’) | 事务A执行结果为空 | 快照读 | |
3 | 插入一条数据(insert into t_user(id, username, pwd, is_deleted, gmt_created, gmt_modified) values(2, ‘aaa’, ‘aaa’, 0, now(), now())) | 事务B执行成功 | 当前读 | |
4 | 提交事务(commit) | 事务B事务提交成功 | ||
5 | 插入一条数据(insert into t_user(id, username, pwd, is_deleted, gmt_created, gmt_modified) values(3, ‘aaa’, ‘aaa’, 0, now(), now())) | 事务A语句执行报错,说username = ‘aaa’ 已经存在 | 当前读 |
总结:MySQL 的 Innodb 在 REPEATABLE-READ 的事务隔离级别下,会发生幻读现象。
原因分析:在一个事务中,先从快照读中读取数据,后从当前读读取数据,会出现数据不一致的情况,因此出现幻读现象。当一个事务中,全程都是快照读,则不会出现幻读现象。