目录
一、什么是mvcc?
二、什么是当前读,快照读?
当前读
快照读
三、mvcc实现原理
版本链
undo日志
Undo log 的用途
Read View(读视图)
Read View几个属性
五、RR、RC级别下生成时机
mvcc全称Multi-Version Concurrency Control,即多版本并发控制
,主要是为了提高数据库的并发性能
,同一行数据平时发生读写请求时,会上锁阻塞
住。但mvcc用更好的方式去处理读—写请求,做到在发生读—写请求冲突时不用加锁
。这个读是指的快照读
,而不是当前读
,当前读是一种加锁操作,是悲观锁
它读取的数据库记录,都是当前最新的版本,会对当前读取的数据进行加锁,防止其他事务修改数据。是悲观锁的一种操作。
如下操作都是当前读:
select lock in share mode (共享锁)
select for update (排他锁)
update (排他锁)
insert (排他锁)
delete (排他锁)
串行化事务隔离级别
快照读的实现是基于多版本
并发控制,即MVCC,既然是多版本,那么快照读读到的数据不一定是当前最新的数据,有可能是之前历史版本
的数据。
如下操作是快照读:
不加锁的select操作(注:事务级别不是串行化)
它的实现原理主要是版本链
,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 里的日志进行数据还原。
保证事务进行rollback时的原子性和一致性,当事务进行回滚的时候可以用undo log的数据进行恢复。
用于MVCC快照读的数据,在MVCC多版本控制中,通过读取undo log的历史版本数据可以实现不同事务版本号都拥有自己独立的快照数据版本。
事务进行快照读操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照。
记录并维护系统当前活跃事务的ID(没有commit,当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以越新的事务,ID值越大),是系统中当前不应该被本事务看到的其他事务id列表。
Read View主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。
trx_ids: 当前系统活跃(未提交)事务版本号集合。
low_limit_id: 创建当前read view 时“当前系统最大事务id。
up_limit_id: 创建当前read view 时“系统正处于活跃事务最小id”
creator_trx_id: 创建当前read view的事务id;
如下图 trx_ids 对应[100,200]是个数组,low_limit_id对应300,up_limit_id则对应100
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产生的时候事务已经commit了,这种情况数据则可以显示。
已存在:则代表我Read View生成时刻,你这个事务还在活跃,还没有Commit,你修改的数据,我当前事务也是看不见的。
RC隔离级别下,是每个快照读都会生成并获取最新的Read View;
而在RR隔离级别下,则是同一个事务中的第一个快照读才会创建Read View, 之后的快照读获取的都是同一个Read View,之后的查询就不会重复生成了,所以一个事务的查询结果每次都是一样的。