简单来说,MVCC(多版本并发控制)就像给数据库拍“快照”。它让事务在读取数据时,看到的是自己开始时的那个“瞬间画面”,而不会受其他事务新增或修改数据的影响。但为什么说它只解决了“部分”幻读呢?我们分三步理解:
1. 什么是幻读?
假设你开了一个事务,要做两件事:
- 第一次查询:查“工资大于5000的员工”,返回10条记录。
- 第二次查询:同样的查询,结果却变成11条了!因为另一个事务在你两次查询之间,偷偷插入了一条新员工数据。
这种“凭空多出来”的现象就是幻读。它破坏了你事务内多次查询结果的一致性。
2. MVCC如何“部分”解决幻读?
- 核心机制:MVCC在事务开始时拍一张“快照”(Read View),之后所有读操作都基于这个快照。
- 效果:
- 其他事务新增的数据(比如那条新插入的员工),在你的快照里根本不存在。
- 所以无论你查多少次,看到的都是同一份数据,避免了幻读。
看起来解决了?但有个漏洞!
3. 为什么只是“部分”解决?
问题出在:事务内如果有更新操作(比如UPDATE、DELETE),会导致“快照”失效!
举个🌰:
- 你第一次查“工资>5000的员工”,返回10条。
- 另一个事务插入了一条工资6000的新员工(id=100),并提交。
- 你执行了一条更新操作:
UPDATE 员工 SET 工资=7000 WHERE 工资>5000
。- 这时MySQL会悄悄升级为“当前读”(Latest Read),直接看最新的数据,而不是快照。
- 于是这条更新操作会影响到新插入的id=100的员工!
- 你再次查询“工资>5000的员工”,会发现id=100这条记录也被更新了,虽然它原本不在你的快照里。
结果:你明明没查到这条数据,但更新时却影响到了它,之后查询又能看到它——这就是MVCC没解决的幻读!
总结
- MVCC防住了“只读不写”的幻读:如果事务内只有查询操作,快照机制能完全避免幻读。
- 但防不住“边读边写”的幻读:一旦事务内有更新操作,MySQL会切换到最新数据,导致新插入的数据被“意外”修改,从而出现幻读。
彻底解决幻读:需要MVCC+间隙锁(Gap Lock)。间隙锁会锁住数据之间的“空隙”,阻止其他事务插入新数据,堵住这个漏洞。所以严格来说,InnoDB在RR级别下,是通过MVCC+间隙锁共同解决幻读的。MVCC单枪匹马只能算“部分解决”。