Site Overlay

InnoDB MVCC机制

事务隔离为了解决事务的并发引起的脏读,重复读,幻读问题。通过添加读锁或者写锁解决,但这会导致读数据的时候没办法修改。修改数据的时候没办法读取,极大的降低了数据库性能。而MVCC多版本控制能优化性能的问题。

例子

背景:MySQL默认的事务隔离级别是可重复读,并发执行事务A和B
事务1:更新id=1那条数据的名字为jennifer,未提交

UPDATE test SET name=jennifer WHERE id = 1;

事务2:查询id=1的那条数据

SELECT * FROM test WHERE id = 1;

执行结果事务2能访问到数据,并且是之前数据。按理说这个操作是更新操作,默认加排他锁,别的事务是不能读也不能写的,为什么能读到呢?–> 读到的是快照中的数据
这就是MVCC,他通过保存数据的历史版本,根据比较版本号来处理数据的是否显示,从而达到读取数据的时候不需要加锁就可以保证事务隔离性的效果。

实现原理

几个知识点
事务版本号:事务开启前从数据库获得一个自增长事务ID,用来判断事务的执行先后顺序
表格隐藏列
DB_TRX_ID:记录操作该数据事务的事务ID
DB_ROLL_PTR:指向上一个版本数据在undo log中的位置指针
DB_ROW_ID:隐藏ID?
Undo log:记录数据被修改前的日志
– 用来回滚时对数据进行恢复
– 用于MVCC快照读取数据
一致性视图(read view):在SQL语句执行前获得。
trx_id: 一个数组,存放活跃(未提交)事务版本号集合
low_limit_id: 创建当前read view 时“当前最大事务版本号+1”。
up_limit_id: 创建当前read view 时“系统正处于活跃事务最小版本号”
creator_trx_id: 创建当前read view的事务版本号;

事务版本号,表格隐藏列和undo log的关系

id name DB_TRX_ID DB_ROLL_PTR
1 zhou 30 xxx
UPDATE test SET name=jennifer WHERE id = 1;

进行更新操作的流程
1. 获得事务ID为31
2. 把这条数据拷贝到undo log日志中
3. 修改这条数据的name为jennifer
4. DB_TRX_ID变为31,DB_ROLL_PTR指向刚刚拷贝到undo log的数据地址

read view的作用

SQL执行前会生成一个read view

| id | name | DB_TRX_ID | DB_ROLL_PTR |
| — | — | — | — |
| 1 | zhou | 30 | xxx |
假设两个事务并发,事务A(txr_id=40)读取这行数据,事务B(txr_id=50)更新这行数据
A读取时Read view的值变为:

trx_ids low_limit_id up_limit_id creator_trx_id
[40, 50] 51 40 40

几种情况
1. 数据trx_id < up_limit_id,数据显示,可以查询这条数据
2. 数据trx_id >= low_limit_id,数据不显示,从undo log中获取历史数据
3. up_limit_id <= 数据trx_id < low_limit_id,数据在当前事务开始时没提交
– 数据trx_id不在trx_ids中,数据显示
– 数据trx_id在trx_ids中,并且数据trx_id = creator_trx_id,数据显示
– 数据trx_id在trx_ids中,并且数据trx_id !
creator_trx_id,数据不显示,从undo log中获取历史数据