mysql的mvcc原理

前提概要

  • 什么是mvcc
  • 什么是当前读和快照读
  • 当前读,快照读和mvcc的关系

MVCC实现原理

  • 隐藏字段
  • undo log(回滚日志)
  • undo log底层实现
  • read-view
  • 版本链对比规则

1. 前提概要

1.1 什么是mvcc
MVCC(Muti-Version Concurrency Control) 多版本并发控制
官方定义:Innodb通过为每一行记录添加两个额外的隐藏的值来实现mvcc,这两个值一个记录这行数据何时被创建,另一个记录着行数据何时过期(或被删除)。但是Innodb并不存储这些事件发生时的实际时间,相反,它只存储这些事件发生时的系统版本号。这是一个随着事务创建而不断增长的数字。每个事务在事务开始会记录他自己的系统版本号。每个查询必须取用检查每行数据的版本号与事务的版本号是否相同。
自我理解:同一时间,不同事物可以读取到不同版本的数据,从而去解决脏读和不可重复读的问题,是innodb实现事务并发与回滚的重要功能。

1.2什么是当前读和快照读?

  • 当前读
    像select lock in share mode(共享锁),select for update;update;insert’delete(排他锁)这些操作都是一种当前读。就是它读取的是记录最新的版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁
  • 快照读
    像不加锁的select操作就是快照读,及不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即mvcc,可以认为mvcc是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读取到的并不一定是数据的最新版本,而有可能是之前的历史版本。
    说白了mvcc就是为了实现读写冲突不加锁,这个读就是快照读。不是当前读,当前读实际上是一种加锁的操作,是悲观锁的实现。
  • 当前读、快照读和mvcc的关系
  • 1.mvcc多版本并发控制是维持一个数据多个版本,是的读写操作没有冲突的概念,只是一个抽象的概念,并非实现
  • 2.因为mvcc只是一个抽象的概念,要实现这么一个概念,mysql就需要根据具体的功能去实现它,快照读就是mysql实现mvcc理想模型的其中一个非阻塞读功能。而相对而言,当前读就是悲观锁的具体功能实现。

mvcc带来的好处

mvcc是一种用来解决读写冲突的无锁并发控制,也就是未事务分配单项增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。所以mvcc可以为数据库解决一下问题

  • 在并发读写数据库时,可以做到在读操作的时候不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能。
  • 同时该可以解决脏读、幻读、不可重复读等事务隔离问题。但不能解决更新丢失问题。

MVCC实现原理

  • mvcc只在REPEATABLE READ(可重复读)和READ COMMITTED(读已提交)这两个隔离级别下适用。
  • mvcc原理实现是由三个隐藏字段、undo日志、read-view实现。

1.隐藏字段

每行数据除了记录我们自定义的字段外,还有数据库隐式定义的三个字段

  • DB_TRX_ID: 6byte,最近修改事务ID(记录创建这条记录/最后一次修改该记录的事务ID)
  • DB_ROLL_PTR: 7byte,回滚指针,指向这条记录的上一个版本
  • DB_ROW_ID: 6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,Innodb会自动以DB_ROW_ID产生一个聚簇索引
    在这里插入图片描述

2.undo log日志

undo log有两个作用,一个是回滚操作实现原子性,另一个作用是实现mvcc的多版本控制。
undo log 分为两种:

-insert undo log
代表事务在insert新纪录时产生的undo log,只在事务回滚时需要,并且在事务提交后立即丢弃

-update undo log
事务在进行update或者delete时产生的undo log;不仅在事务回滚时需要,在快照读也需要;所以不能随便删除,只有在快照读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除

-undo log 在mvcc中的作用
undo log保存的是一个版本链,也就是使用DB_ROLL_PTR这个字段来连接。undo log在mvcc中就是为了根据存储的事务ID和一致性试图做对比,找出当前事务能够看到的版本数据。

3.undo log底层实现

假设一开始的数据如下
在这里插入图片描述
然后执行了一条更新sql,update user set name ='niuniu' where id = 1,那么undo log的记录就会发生变化,也就是说当执行一条更新语句时会把之前的原有数据拷贝到undo log日志中。同时在最新的数据记录中的DB_ROLL_PTR储存指向undo log的指针地址。
在这里插入图片描述
在这里插入图片描述
下面蓝色那部分时 undo log日志记录,如上图那样。

4.read-view

ReadView可以理解为数据库中某一时刻所有未提交事务的快照。ReadView有几个重要的参数:

  • m_ids: 表示生成ReadView时,当前系统正在活跃的读写事务的事物id列表。(也就是还未提交事务)
  • min_trx_id: 表示生成ReadView时,当前系统中活跃的读写事物的最小事务id。
  • max_trx_id: 表示生成Readview时,当前时间戳Innobd将在下一次分配事务的id。
  • creator_trx_id: 当前事务id

用处:配合undo log进行版本链对比,比对出当前事务可见的版本数据。

5.版本链比对规则

  • 1.如果落在trx_id < min_id,表示此版本的事务在生成readview前已经提交,所以该版本的数据可以被当前事务访问。
  • 2.如果落在trx_id > max_id, 表示此版本的事务在生成readview后才生成,所以该版本不可以被当前事务访问。
  • 3.如果被访问版本的trx_id属性值在m_ids列表的最小事务id和最大事务id之间,就需要进一步判断trx_id属性值是不是包含在m_ids中,如果包含的话,说明创建readview时生成的该版本的事务还是活跃的,所以该版本不可以访问;如果不包含,说明创建readview时生成该版本的事务已经被提交,该版本可以访问。

6.示例

假如有一条数据user数据,初始值name=“刘德华”,然后经过下面的更新,时间点如下:
在这里插入图片描述
1.基于RC隔离级别的事务在每次查询开始的时候都会生成一个独立的ReadView.
在T4时间点时,版本链如下:
在这里插入图片描述
执行update语句,undo log记录一份修改之前的数据,然后更新后的数据后面记录当前事务id100,还有指向undo log的指针地址。如上图所示。因为此时系统活跃的事务由100和200都未提交,所以生成的readview事务列表m_ids=[100,200],然后根据版本比对规则,300>max_trx_id(200),证明此版本数据对当前事务不可见,然后根据回滚指针找到上一个版本接着比对,直到找到数据刘德华。
在T6时间点,版本链如下:
在这里插入图片描述
在T6时间点select语句执行时,当前系统正在活跃的事务还有trx_id未200未提交,所以m_ids=[200].因此找到的数据就是古天乐。

2.基于RR隔离级别的事务在第一次读取数据时生成ReadView,之后的查询都不会再生成,所以一个事务的查询结果每次都是一样的。(注意特例:如果在两次快照读之间穿插一个当前读会重新生成ReadView)
第一次查询也就是T4时间点生成ReadView,事务列表m_ids=[100,200].所以当前版本可见数据为刘德华。
第二第三次不会生成新的ReadView,所以结果都是刘德华。
由于在同一个事务中,RR级别的事务在查询中只会生成一个ReadView,所以能解决不可重复读问题。

小结

在这里插入图片描述

  • 如果当前事务id在绿色部分,是已经提交了事务,说明数据可见。
  • 如果当前事务id在蓝色部分,会有两种情况,如果当前事务id在readview的m_ids数组内,是没有提交的事务不可见,如果不在数组内数据可见。
  • 如果落在红色部分,则不考虑,对于未来的事情不去想即可。

借鉴:https://zhuanlan.zhihu.com/p/500868047
https://zhuanlan.zhihu.com/p/428066667
https://zhuanlan.zhihu.com/p/367820387

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值