1、MVCC是什么?
MVCC的核心思想:读不加锁,读写不冲突,通过数据的多个版本实现并发控制
它的实现依赖三个东西:
1、隐藏字段
每行数据有DB_TRX_ID (最近修改该行的事务ID ),DB_ROLL_PTR(回滚指针)
2.Undo Log 版本链
记录数据的历史版本,构成版本链
3.Read View
事务快照,决定当前事务能看到哪些版本
2、MVCC如何工作?
1.版本链
每次更新一行数据时,InnoDB会:
1.把旧版本写入Undo Log
2.新版本指向旧版本(通过回滚指针 )
3.形成一条版本链
bash
当前版本→上个版本→更早版本
2.Read View
事务开始读取时,生成一个Read View,包含:
- m_ids: 当前活跃事务ID 集合
- min_trx_id:活跃事务中的最小ID
- max_trx_id:下一个即将分配的事务ID
- **creator_trx_id:**当前事务自己的ID
然后沿着版本链,按规则判断哪个版本对应当前事务可见。
3.核心关系:不同隔离级别,Read View 生成时机不同
这是理解 MVCC与事务隔离级别关系最关键的一部分
RU 读未提交
基本不用MVCC ,直接读最新版本
- 不生成Read View
- 直接读取当前最新数据,不管是否已提交
- 所以会出现脏读
RC 读已提交
每次SELECT 都生成一个新的Read View
这意味着:
- 每次查询都能看到别的事务最新提交的数据
- 所以同一个事务里两次查询,结果可能不同 ,造成不可重复读
RR 可重复读
只在第一次SELECT时 生成Read View ,之后复用同一个
这意味着:
- 整个事务期间,看到的都是同一个快照
- 别的事务提交了更新,也看不到,这样就避免了不可重复读
Serializable 串行化
MVCC 退化为基于锁的并发控制
- 所有SELECT 自动加 lock in share mode
- 读写相互阻塞
- MVCC的快照基本不再使用
4、MVCC能解决什么,不能解决什么
可以解决:
- 脏读:通过 版本链+Read View 只读 已提交的版本
- 不可重复读:RR 级别复用 Read View,保持快照一致
- 普通的并发性能:读不加锁,读写不阻塞
不能完全解决的:
- 幻读: MVCC的快照只能保证读到的行不变 ,但不能阻止别的事务插入新的行
5、那幻读怎么办? MVCC+锁 一起上!
在 RR 隔离级别下,InnoDB用 MVCC+Next-Key Lock 组合拳来对付幻读
场景:
普通SELECT 快照读
MVCC,读历史快照,看不到新插入的行
SELECT ... FOR UPDATE 当前读
Next-Key Lock,锁住间隙,阻止插入
UPDATE/DELETE 当前读
Next-Key Lock 同样锁间隙
所以:
MVCC 负责快照读的一致性 ,Next-Key Lock 负责当前读时防止幻读
6、总结
MVCC是InnoDB实现事务隔离的底层机制
不同隔离级别的本质区别在于Read View的生成时机不同
RC读已提交 每次查询都生成新的Read View
RR可重复读 只生成一次并复用
MVCC负责快照读的一致性 ,配合Next-Key Lock 解决当前读的幻读问题