事务隔离为了解决事务的并发引起的脏读,重复读,幻读问题。通过添加读锁或者写锁解决,但这会导致读数据的时候没办法修改。修改数据的时候没办法读取,极大的降低了数据库性能。而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中获取历史数据