面试不可或缺的MVCC
什么是MVCC? 以及它的作用
“MVCC,即多版本并发控制。顾名思义,MVCC就是通过对数据做多版本管理,来做到读不加锁,读写不冲突,进而提高数据库并发性能。在Innodb存储引擎中MVCC用来实现RR和RC两种事物隔离级别,即保证在这两种隔离级别下进行读操作时,读到的总是我们想要的数据。 ”
预设行数据
如下图所示,插入一条id为1,name为大西瓜的记录
快照读和当前读
- 快照读:读取的是版本链中的可见版本,通过MVCC实现并发控制,SELECT操作;
- 当前读:读取的是最新版本,通过Next-Key锁来保证该记录的正确性,INSERT/UPDATE/DELETE这些可能会导致数据库数据更新的操作。
MVCC的原理支持
- 隐藏字段
- UndoLog
- ReadView
隐藏字段:
- row_id : 在Innodb引擎下,当我们不手动指定主键时,由于Innodb是基于B 树索引的,Innodb会自动生成一个大小为6B的隐藏字段,来充当我们的主键(聚簇索引)用来进行生成B 树索引组织数据。
- trx_id : 表示修改这条记录的事务id,大小6B
- roll_pointer : 回滚指针,指向Undo log中,该记录之前的版本数据,大小为7B
此时我在上面提到的预设行,实际上会是下图这种格式,roll_pointer为null是因为INSERT操作的记录没有更早的版本,即不存在该属性,但是为了大家理解方便,这里表示为null:
UndoLog:
“回滚日志,版本快照是保存在Undo日志中的,版本快照之间又是通过回滚指针roll_pointer链接起来的,这样就生成了版本链。 ”
假设存在两个id为100、200的事务对预设字段进行了更新操作,操作流程为:
trx_id=100 | try_id=100 |
---|---|
Begin | |
Begin | |
update user set name = "小西瓜" where id = 1; | |
update user set name = "西瓜籽" where id = 1; | |
Commit | |
update user set name = "大冬瓜" where id = 1; | |
upda te user set name = "小冬瓜" where id = 1; | |
Commit |
则其对应生成的版本链应当为:
ReadView:
MVCC之所以可以做到版本并发控制,很大程度上是依托于ReadView。我们前面提到过MVCC是用来实现RR和RC两种事务隔离级别的,当然在这两种隔离级别下的ReadView生成策略也是不同的:
- RR级别下事务只在第一次进行快照读时创建ReadView视图,保证了可重复读这一特性。
- RC级别下事务每次进行快照读都会创建新的ReadView视图,保证了事务可以看到其他事务修改并提交后的数据,保证了读已提交这一特性
ReadView的几个重要属性:
- trx_ids:表示在创建ReadView时,系统中未提交(活跃)事务版本号集合
- min_trx_id:表示在创建ReadView时,系统中未提交事务中最小版本号
- max_trx_id:表示在创建ReadView时,系统中应该分配给下一个事务的id值,即当前系统中已创建的最大事务版本号 1;
- creator_trx_id:创建当前ReadView的事务版本号,只有INSERT/DELETE/UPDATE这些可能存在对数据库发生修改的操作才会分配事务id,只读的SELECT事务的id值为0;
ReadView的匹配规则:
- 被访问版本的trx_id < min_trx_id,表示生成该版本的事务在当前事务生成ReadView前就提交了,可见
- 被访问版本的trx_id >= max_trx_id,表示生成该版本的事务在当前事务生成ReadView后才开启的,不可见
- 被访问版本的trx_id ∈ [min_trx_id,max_trx_id),这时候分三种情况讨论:①:trx_id ∈ trx_ids,表示生成该版本的事务还未提交,不可见 ②:特例:trx_id = creator_trx_id,表示当前事务正在访问自己修改过的版本记录,可见 ③:trx_id ∉ trx_ids,表示生成该版本的事务已经提交,可见