MySQL 的 MVCC 到底解决了幻读问题没有?请举例说明。

MySQL 的 MVCC(多版本并发控制)并未完全解决幻读问题,仅在特定隔离级别(Repeatable Read,可重复读)下缓解了部分幻读场景,无法处理"当前读"引发的幻读。

先说结论

① mysql可重复读不能完全避免幻读

② 幻读的出现,往往是在一个事务里面混用了快照读和当前读。

③ 想彻底避免幻读,就在事务一开始,把普通的select 换成 select ... for update 直接加锁,把这个范围锁住,别的数据就无法进行数据的插入干扰了。

1. 先明确:MVCC 如何"缓解"幻读(快照读场景)

InnoDB 的 MVCC 核心是通过"快照读"(如 SELECT * FROM table)读取事务启动时的数据快照,避免读取其他事务新增/删除的数据,从而在 Repeatable Read 级别下实现"重复读",间接减少幻读。

示例(快照读无幻读):

假设表 user 初始数据:id=1, name='A',当前隔离级别为 Repeatable Read。

事务1(快照读):

1.启动事务,执行 SELECT * FROM user WHERE id > 0,结果为 [id=1](读取的是事务启动时的快照)。

2.此时事务2启动,执行 INSERT INTO user (id, name) VALUES (2, 'B') 并提交。

3.事务1 再次执行 SELECT * FROM user WHERE id > 0,结果仍为 [id=1](未读取到事务2新增的 id=2)。

此场景下,MVCC 通过快照读避免了"第二次查询出现新数据"的幻读,因为事务1始终读的是自己启动时的快照。

2. MVCC 无法解决的幻读(当前读场景)

"当前读"(如 SELECT ... FOR UPDATE、UPDATE、DELETE)会读取最新的数据库数据(而非快照),此时若其他事务已新增符合条件的数据,会导致"当前读"读到新数据,产生幻读------这是 MVCC 无法解决的。

示例(当前读仍有幻读):

沿用上述表 user 初始数据,隔离级别仍为 Repeatable Read。

事务1(包含当前读):

1.启动事务,执行 SELECT * FROM user WHERE id > 0(快照读),结果为 [id=1]。

2.事务2启动,执行 INSERT INTO user (id, name) VALUES (2, 'B') 并提交。

3.事务1 执行 SELECT * FROM user WHERE id > 0 FOR UPDATE(当前读,锁定数据),结果为 [id=1, id=2](读到了事务2新增的数据)。

此场景下,事务1的"当前读"出现了"第一次没读到、第二次读到"的幻读,MVCC 无法避免------因为当前读的逻辑是"读最新数据",而非快照。

结论

  • MVCC 仅在 Repeatable Read 级别下的"快照读"场景 中,通过数据快照避免了幻读

  • 对于 "当前读"场景(如加锁查询、更新/删除),MVCC 无法解决幻读,需通过更高隔离级别(如 Serializable,串行化)或手动加表锁来彻底避免

相关推荐
小白银子11 分钟前
零基础从头教学Linux(Day 62)
数据库·mysql·oracle
Boilermaker19923 小时前
【MySQL 进阶】高性能优化
数据库·sql·mysql
CoderOnly4 小时前
SQL,CROSS JOIN速度优化
数据库·sql·mysql
老衲提灯找美女6 小时前
MySQL的增删改查功能合集
数据库·mysql·增删改查·增删改查详细用法
Doro再努力7 小时前
MySQL数据库07:分组查询与分类查询
数据库·mysql
弥生赞歌8 小时前
Mysql作业四
数据库·mysql
han_9 小时前
前端高频面试题之Vue(初、中级篇)
前端·vue.js·面试
掘金安东尼9 小时前
TypeScript为何在AI时代登顶:Anders Hejlsberg 的十二年演化论
前端·javascript·面试
爱学测试的雨果11 小时前
14:00面试,14:06就出来了,问的问题过于变态了。。。
面试·职场和发展
Lethehong12 小时前
百万迁移费成历史?金仓数据库“零代码”替换Oracle,我们扒了扒它的技术底牌
后端·mysql·架构