MVCC即多版本并发控制,是一种并发控制方法,一般在数据库管理系统上,实现对数据库的并发访问,在编程语言上实现事务内存<br />MVCC再Mysql的InnoDB引擎中的实现主要是为了提高数据库并发性能,用更好的方式去处理读写冲突,做到即使有读写冲突时,也能做到不加锁,非堵塞并发读<br />当隔离级别是重复度的时候,有些时候会避免幻读<br />MVCC的具体实现是由mysql的**undo log,隐藏字段,Read View,**去完成的
当前读
像加锁的读取就是当前读,或者inset,update,delete 都是当前读,读取的是数据库表中的最新记录,读取的时候还要保证其他并发事务不能修改当前记录,会对读取的记录加锁
快照读
就是不加锁的selectl,最普通的select查询sql,快照读的前提是隔离级别不是穿行级别,
快照读的实现是基于多版本控制器(MVCC),可以认为是数据行锁的一个变种,但在很多诺情况下,避免了加锁的操作,避免了加锁操作,降低了开锁,既然是基于多版本,读取到的数据可能读到的不一定是数据的最新的版本,有可能是之前的例是版本,
作用
- 在并发读写数据库时,可以做到在读操作时不用堵塞写操作,写操作也不用堵塞读操作,提高了数据库并发读写的性能,
- 同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但是不能解决更新丢失问题,
隐式字段
之前看过了,这次使用其中的两个字段,
DB_TRX_ID :最近修改的事务id,记录这条记录添加,最后修改的事务ID
DB_ROLL_PTR :回滚指针,指向这条记录的上一个版本
undo 日志
分为两种
insert undo log:代表事务在insert新记录时产生的undo log,只是在事务回滚时需要,并且在事务提交之后被立刻丢弃
update undo log: 事务在进行update,delete时产生的undo log,不仅在事务回滚时需要,在快照读的时候也需要,所有不会随便删除,只有在快照读,事务回滚不涉及该日志时,对应的日志才会被purge线程同意清除
对mvcc有帮助的实质是update log,log中实际上就是存在数据之前版本的旧记录链
- 比如一个事务添加了一条数据,记录如下,隐式主键1,事务id跟回滚假设为null
- 现在有了一个事务1,对name,做了修改,改为tom
在事务1修改数据的时候,会先加排他锁,
将数据拷贝一份到undo log 中作为旧纪录,这样在undo log 中就出现了当前行的拷贝副本,
拷贝完毕,修改数据,并且修改隐藏字段,事务id为当前事务id,默认先从1开始,之后递增,回滚指针指向拷贝到undo log 的副本记录,表示我的上一个版本就是它
事务提交后,释放锁
- 这时又来了一个事务2,修改同一个记录数据,age修改为30随
在修改事务时,先加锁
将数据赋值一份到undo log 中,发现该行记录已经有undo log 了 那么最新的旧数据,作为链表的表头插入到该行记录的undo log 最前面
修改记录,事务id为当前事务id2,回滚指针指向刚才拷贝的最新旧数据
事务提交后,释放锁
这样我们就可以看出,不同的事务或者相同的事务对同一记录的修改,会导致该记录的undo log 称为一条记录版本的线性表,链表,链首就是最新的数据,链尾是最早的记录,
Read View 读视图
事务进行快照读操作的时候,生产的读视图,在事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护当前活跃事务id(当每个事务开启时,都会被分配一个事务id,递增的,最新的id值越大)
当事务隔离级别是读已提交时,每次执行快照读都会生成一次读视图,而重复度隔离级别不会这样,仅在第一次执行快照读的 时候生成读视图,后续快照复用(这样就避免了幻读,多次都是使用一个读视图),当然也有例外,就是两次快照读中间存在一次当前读,读视图就会重新生成,这样就会导致幻读。
read view是一个数据结构,包含四个字敦
m_ids:当前活跃的事务编号集合
min_trx_id:最小活跃事务编号id
max_trx_id:预分配事务id,当前最大事务id+1
creater_trx_id:读视图的创建者事务id
读视图主要是用来做可见性判断的,当我们某个事务执行快照读的时候,对该记录创建一个读视图,把它当作条件用来判断,当前事务能够看到哪个版本的数据,可能时当前最新的数据,可以能会是undo log 里面的某个版本数据记录
判断逻辑及算法:是根据要被修改数据的undo log 中最新记录中的DB_TRX_ID 当前事务id,取出来,进行判断
整体流程
当事务执行快照读的时候从隐藏字段中找到当前数据的undo log 日志,获取到最新的版本记录,拿到最新记录的版本id,开始判断该版本记录的可见性
与读视图参数进行判断
- 判断当前事务id是否等于creater_trx_id(创建者事务id),如果成立说明数据就是自己这个事务更改的,可以直接访问,返回该版本记录
- 判断事务id小于min_trx_id(最小),如果成立说明数据已经提交了,返回该版本数据
- 判断事务id大于max_trx_id(最大),如果成立说明该事务实在read view生成之后才开启的,不能返回
- 判断事务id大于等于最小id,小于等于最大id,(min_trx_id<=事务id<=max_trx_id)则在m_ids中进行比对,不存在的话说明该事务已经提交,返回该版本记录
当以上条件都不成立,都不能返回版本记录时,获取到最新版本记录的上个版本指针,取下一条的版本记录,
继续重复上面的判断,直到返回数据,