MySQL幻读问题

幻读指的是一个事务开启之后,执行了两次相同的 SELECT 查询某一范围内的数据,但是第二次查询返回了第一次未返回的行,也就是读取到了幻行,这就是幻读问题。

MySQL 官方也将这个问题叫做幻象问题,读取到的行叫做幻行。
地址:https://dev.mysql.com/doc/refman/8.0/en/innodb-next-key-locking.html

如何解决幻读问题?

在默认的可重复读(RR)隔离级别下:
针对快照读,InnoDB 通过 MVCC 的方式解决幻读问题,原理就是在事务开始之前就会生成一个 ReadView ,后续的查询都是基于这个 ReadView 进行的。

注意:如果在快照读期间执行了当前读,则 ReadView 会失效,进而产生幻读问题。

针对当前读,InnoDB 通过 Next-key Lock 解决幻读问题,原理就是 InnoDB 不止锁定查询中涉及的行,还会对索引结构中的间隙进行锁定,防止幻行被插入。

快照读:读取某一时刻生成的最新快照数据,即普通的 SELECT 语句,例如 SELECT * FROM t WHERE xx = yy
当前读:读取最新提交的数据,即 UPDATE、INSERT 、DELETE 以及 SELECT ... FOR UPDATE 的语句。

在串行化(SERIALIZABLE)隔离级别下:该隔离级别通过强制事务按序执行,使得不同事务之间不可能产生冲突,从而解决了幻读问题。

为什么可重复读隔离级别下的 MVCC没法彻底的解决幻读问题?

MVCC 解决幻读主要是针对快照读而言的,但是如果在快照读期间执行了当前读,比如 UPDATE、INSERT、DELETE、SELECT ... FOR UPDATE 等操作,那么快照读的 ReadView 就会失效,此时当前读会去读取最新的数据,进而产生幻读问题。
案例一:
当前表 tp 的数据如下所示:
image.png
事务 A 的操作

BEGIN;
#执行快照读
SELECT * FROM tp WHERE id = 5; #没数据

...执行事务B

#执行当前读,尝试更新id=5的数据
UPDATE tp SET age = 500 WHERE id = 5;

#执行快照读,再次查询id=5的数据
SELECT * FROM tp WHERE id = 5; #查询到了数据,此时事务还没提交哦!两次查询的结果不一样,发生了幻读。
.........
COMMIT;

事务 B 的操作

BEGIN;
#插入一条数据
INSERT INTO tp VALUE(5,50)
COMMIT;

案例二:
当前表 tp 的数据如下所示:
image.png
事务 A 的操作

BEGIN;
#执行快照读,查询年龄大于10的所有数据,查询到20,30,40三条数据
SELECT * FROM tp WHERE age > 10;

....事务B执行

#执行当前读,依旧查询年龄大于10的所有数据
SELECT * FROM tp WHERE age > 10 FOR UPDATE;
竟然把事务B刚插入的数据(5,50)也查询到了,发生了幻读。

....
COMMIT;

事务 B 的操作

BEGIN;
#插入数据
INSERT INTO tp VALUE(5,50);
COMMIT;
posted @   程序员花卷  阅读(34)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示