目录
一、什么是MVCC
二、当前读、快照读都是什么鬼
三、当前读
四、快照读
五、数据库的并发场景
六、MVCC解决并发的哪些问题
1.解决问题如下:
七、MVCC的实现原理
1.版本链
八、undo日志
1.undo log 的用途
2.undo log主要分为两种
九、Read View(读视图)
1.Read View几个属性
2.Read View可见性判断条件
十、MVCC和事务隔离级别
十一、RR、RC生成时机
1.解决幻读问题
2.RC、RR级别下的InnoDB快照读区别
十二、总结
全称Multi-Version Concurrency Control,即多版本并发控制,主要是为了提高数据库的并发性能。以下文章都是围绕InnoDB引擎来讲,因为myIsam不支持事务。
同一行数据平时发生读写请求时,会上锁阻塞住。但mvcc用更好的方式去处理读—写请求,做到在发生读—写请求冲突时不用加锁。
这个读是指的快照读,而不是当前读,当前读是一种加锁操作,是悲观锁。
那它到底是怎么做到读—写不用加锁的,快照读和当前读又是什么鬼,跟着你们的贴心老哥,继续往下看。
什么是MySQL InnoDB下的当前读和快照读?
它读取的数据库记录,都是当前最新的版本,会对当前读取的数据进行加锁,防止其他事务修改数据。是悲观锁的一种操作。
如下操作都是当前读:
快照读的实现是基于多版本并发控制,即MVCC,既然是多版本,那么快照读读到的数据不一定是当前最新的数据,有可能是之前历史版本的数据。
如下操作是快照读:
快照读与MVCC的关系
答案:MVCCC是“维持一个数据的多个版本,使读写操作没有冲突”的一个抽象概念。
这个概念需要具体功能去实现,这个具体实现就是快照读。(具体实现下面讲)
听完贴心老哥的讲解,是不是瞬间茅厕顿开。
mvcc用来解决读—写冲突的无锁并发控制,就是为事务分配单向增长的时间戳。为每个数据修改保存一个版本,版本与事务时间戳相关联。
读操作只读取该事务开始前的数据库快照。
因此有了下面提高并发性能的组合拳 :
它的实现原理主要是版本链,undo日志 ,Read View 来实现的
我们数据库中的每行数据,除了我们肉眼看见的数据,还有几个隐藏字段,得开天眼才能看到。分别是db_trx_id、db_roll_pointer、db_row_id。
db_trx_id:
6byte,最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID。
db_roll_pointer(版本链关键):
7byte,回滚指针,指向这条记录的上一个版本(存储于rollback segment里)
db_row_id:
6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以db_row_id产生一个聚簇索引。
实际还有一个删除flag隐藏字段, 记录被更新或删除并不代表真的删除,而是删除flag变了
如上图,db_row_id是数据库默认为该行记录生成的唯一隐式主键,db_trx_id是当前操作该记录的事务ID,而db_roll_pointer是一个回滚指针,用于配合undo日志,指向上一个旧版本。
每次对数据库记录进行改动,都会记录一条undo日志,每条undo日志也都有一个roll_pointer属性(INSERT操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些undo日志都连起来,串成一个链表,所以现在的情况就像下图一样:
对该记录每次更新后,都会将旧值放到一条undo日志中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被roll_pointer属性连接成一个链表,我们把这个链表称之为版本链,版本链的头节点就是当前记录最新的值。另外,每个版本中还包含生成该版本时对应的事务id,这个信息很重要,在根据ReadView判断版本可见性的时候会用到。
Undo log 主要用于记录数据被修改之前的日志,在表信息修改之前先会把数据拷贝到undo log里。
当事务进行回滚时可以通过undo log 里的日志进行数据还原。
insert undo log:
代表事务在insert新记录时产生的undo log , 只在事务回滚时需要,并且在事务提交后可以被立即丢弃
update undo log(主要):
事务在进行update或delete时产生的undo log ; 不仅在事务回滚时需要,在快照读时也需要;
所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除
事务进行快照读操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照。
记录并维护系统当前活跃事务的ID(没有commit,当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以越新的事务,ID值越大),是系统中当前不应该被本事务看到的其他事务id列表。
Read View主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。
db_trx_id < up_limit_id || db_trx_id == creator_trx_id(显示)
如果数据事务ID小于read view中的最小活跃事务ID,则可以肯定该数据是在当前事务启之前就已经存在了的,所以可以显示。
或者数据的事务ID等于creator_trx_id ,那么说明这个数据就是当前事务自己生成的,自己生成的数据自己当然能看见,所以这种情 况下此数据也是可以显示的。
db_trx_id >= low_limit_id(不显示)
如果数据事务ID大于read view 中的当前系统的最大事务ID,则说明该数据是在当前read view 创建之后才产生的,所以数据不显示。 如果小于则进入下一个判断
db_trx_id是否在活跃事务(trx_ids)中
上面所讲的Read View用于支持RC(Read Committed,读提交)和RR(Repeatable Read,可重复读)隔离级别的实现。
从以上的描述中我们可以看出来,所谓的MVCC指的就是在使用READ COMMITTD、REPEATABLE READ这两种隔离级别的事务在执行普通的SEELCT操作时访问记录的版本链的过程,这样子可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。