脚本宝典收集整理的这篇文章主要介绍了InnoDB学习(五)之MVCC多版本并发控制,脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。
MVCC多版本并发控制,是一种数据库管理系统并发控制的方法。MVCC多版本并发控制下,数据库中的数据会有多个版本,分别对应不同的事务,从而达到事务之间并发数据的隔离。MVCC最大的优势是读不加锁,读写不冲突,在读多写少场景中,读写不冲突可以大幅提升数据库的并发性能。
在MySQL中,MyISam存储引擎使用的是表锁,InnoDB存储引擎使用的是行锁。而InnoDB的事务分为四个隔离级别,其中默认的隔离级别是可重复读,可重复读要求两个并行的事务之间数据的修改互不影响,通过添加行锁的方式虽然可以实现两个事务之间数据据的修改互不影响,但是者两个事务之间存在锁等待的情况,影响数据库效率。所以InnoDB的可重复读没有采用行锁,而是使用了更为强大的MVCC。
MVCC只有在可重复读和读已提交的隔离级别下生效,其它两个隔离级别和MVCC不兼容,因为读未提交总是读最新的数据行,和事务版本无关,串行化则是会对所有读取的行加锁。由于可重复读的情况比较复杂,并且是MySQL的默认隔离级别,所以本文会用可重复读来讲解MVCC的原理。
数据库有四种隔离级别:读未提交/读已提交/可重复读/串行化,可重复度是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到一致的数据行。
数据行的一致性包含两部分:
InnoDB默认的隔离级别是可重复读,可以解决以上两种情况的数据行一致性问题。其中解决情况1中的数据行一致性问题就是通过MVCC多版本并发控制实现的。
InnoDB用过Gap锁实现情况2中的数据行一致性问题,不过本文不会对Gap锁进行介绍。
MVCC可以确保同一个事务,在事务起始到结束读到的某一个数据是一致的,并且多个事务之间互不阻塞。我们以一张用户表为例,说明MVCC版本控制的作用。
首先我们需要创建用户表,并向其中插入一条用户数据,SQL语句如下:
create table user_info
(
age int ,
name vArchar(255)
);
insert into user_info(age,name) value (23,'张三');
假设有A,B,C三个事务,这三个事务中在不同时刻对读取了插入用户的信息,并对用户信息进行了修改,时间线如下:
age=23
的用户,该用户的name
为张三
;age=23
的用户,该用户的name
为张三
;age=23
的用户,把name
修改为李四
;age=23
的用户,该用户的name
为李四
,事务A提交事务;age=23
的用户,该用户的name
为张三
,事务B提交事务;age=23
的用户,该用户的name
为李四
,事务C提交事务;MVCC的作用可以在T5时刻体现出来,此时事务A已经提交,并且修改age=23
的用户的name
为李四
,但是事务B看不到这次修改,事务B看到的age=23
的用户的name
为张三
。这是因为在可重复度的隔离级别下,InnoDB事务读取到的数据是快照读
,即事务B开始时为数据生成一个快照,事务B读到的数据始终都是这个快照,与快照读
对应的是当前读
:
MVCC的目的就是多版本并发控制,在InnoDB中引入MVCC就是为了解决读写冲突,MVCC主要包含三部分内容:数据库中的3个隐藏字段、UndoLOG日志 、ReadView读视图,这三部分在MVCC中的作用分别如下所示:
隐藏字段意味着我们通过SQL语句查找不到这些字段,但是这些字段在数据库中实际存在并占用了存储空间。为了实现MVCC版本控制,InnoDB为每一行数据添加了以下3个隐藏字段:
DB_TRX_ID
:6字节,最后修改本记录的事务ID;DB_ROLL_PTR
:7字节,回滚指针,指向这条记录的上一个版本(存储于Rollback Segment);DB_ROW_ID
:6字节,隐藏主键,如果数据表没有显式主键,InnoDB用DB_ROW_ID构建聚簇索引;我们使用以下SQL创建用户表,并向表中插入一条数据,新表会默认包含三个隐藏字段,表结构如下表所示。
create table user_info
(
age int,
name VARchar(255)
);
insert into user_info(age,name) value (23,'张三');
|age|name|DB_TRX_ID|DB_ROLL_PTR|DB_ROW_ID| |--|--|--|--|| |23|张三|1|0x222333|1|
我在另外一篇文章中介绍过UndoLog日志,从名字也可以看出来,UndoLog日志主要用于回滚事务。但是InnoDB中的MVCC的快照读也使用了UndoLog。UndoLog可以分为两大类:
Purge线程:InnoDB中,被删除的数据不会直接删除,而是先标记为删除,无用的Update UndoLog也不会立即删除。这些数据都是通过InnoDB中的后台任务Purge线程进行删除的。
下文中我们以上文中的用户表以及数据为例,解释Update UndoLog的工作流程,如下为起始时user_info
表空间的数据状态:
T1时刻,事务A开始,事务Id为2,事务A读取age=23
的用户,该用户的name
为张三
;此时没有修改数据库数据,没有生成UndoLog,表空间无变化;
T2时刻,事务B开始,事务Id为3,事务B读取age=23
的用户,该用户的name
为张三
;此时没有修改数据库数据,没有生成UndoLog,表空间无变化;
T3时刻,事务A修改age=23
的用户,把name
修改为李四
;此时由于事务A尚未提交,所以会给事务A生成一条UndoLog,UndoLog中存储了事务A修改前的数据,表空间中最新数据中的回滚指针指向这条日志;
T4时刻,事务A读取age=23
的用户,由于表数据中的记录的事务ID和事务A的事务ID一致,所以事务A会读取到表数据中的记录,读取到用户的name
为李四
,事务A提交事务;
T5时刻,事务B读取age=23
的用户,由于表空间中数据不满足可见性条件(下一节具体介绍),所以事务B会查找表数据的UndoLog,UndoLog中的数据满足可见性条件,所以查询到UndoLog中的用户,用户的name
为张三
,事务B提交事务;
T6时刻,事务C开始,事务ID为3,事务C读取age=23
的用户,由于事务C开始时事务A已经提交,所以事务C可以查询到已提交的数据,事务C读取到用户的name
为李四
;
T7时刻,事务C开始,事务ID为3,事务C修改age=23
的用户,把name
修改为王五
;此时由于事务C尚未提交,所以会给事务C生成一条UndoLog,UndoLog中存储了事务C修改前的数据;
从上面的例子可以看出,不同事务或者相同事务的对同一记录的修改,会导致该记录的UndoLog成为一条记录版本线性链表,UndoLog的链首就是最新的旧记录,链尾就是最早的旧记录(UndoLog的节点可能会被Purge线程清除掉)
UndoLog是为回滚而用,具体内容就是复制事务前的数据库记录行到UndoBuffer,在适合的时间把UndoBuffer中的内容刷新到磁盘。UndoBuffer与redoBuffer一样,也是环形缓冲,但当缓冲满的时候,UndoBuffer中的内容会也会被刷新到磁盘;与RedoLog不同的是,磁盘上不存在单独的UndoLog文件,所有的UndoLog均存放在主ibd数据文件中(表空间),即使客户端设置了每表一个数据文件也是如此。
ReadView就是事务进行快照读操作的时候生产的读视图,在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)
所以我们知道ReadView主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个ReadView读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的UndoLog里面的某个版本的数据。
ReadView遵循一个可见性算法,主要是将要被修改的数据的最新记录中的DB_TRX_ID(即当前事务ID)取出来,与系统当前其他活跃事务的ID去对比(由ReadView维护),如果DB_TRX_ID跟ReadView的属性做了某些比较,不符合可见性,那就通过DB_ROLL_PTR回滚指针去取出UndoLog中的DB_TRX_ID再比较,即遍历链表的DB_TRX_ID(从链首到链尾,即从最近的一次修改查起),直到找到满足特定条件的DB_TRX_ID, 那么这个DB_TRX_ID所在的旧记录就是当前事务能看见的最新老版本。
ReadView判断可见性的原理如下,在InnoDB中,创建一个新事务之后,当新事务读取数据时,数据库为该事务生成一个ReadView读视图,InnoDB会将当前系统中的活跃事务列表创建一个副本保存到ReadView。当用户在这个事务中要读取某行记录的时候,InnoDB会将该行当前的版本号与该ReadView进行比较。具体的算法如下:
总结一下:MVCC版本控制中,以事务第一次快照读为分界线,事务后续只能查找到第一次快照读及之前提交的数据版本,之后提交的数据版本不可见。
读已提交和可重复度隔离级别下的InnoDB快照读有什么不同?答案是:ReadView生成时机的不同,从而造成读已提交和可重复度级别下快照读的结果的不同:
总之在读已提交隔离级别下,是每个快照读都会生成并获取最新的ReadView;而在可重复读隔离级别下,则是同一个事务中的第一个快照读才会创建ReadView, 之后的快照读获取的都是同一个ReadView。
幻读是指,同一个事务里面连续执行两次同样的SQL语句,可能导致不同结果的问题,第二次SQL语句可能会返回之前不存在的行。举例说明:T1时刻事务A和事务B同时开启,分别进行了快照读,然后事务A向数据库中插入一条新的记录,如果事务B可以读到这条记录,就出现了"幻读",因为B第一次快照读没有读到这条数据。
MVCC是否可以解决幻读问题呢?答案是有的情况下可以解决,有的情况下不可以解决。如果事务B中的读是快照读,那么MVCC版本控制可以解决幻读问题;如果事务B中使用的是当前读,那么MVCC无法解决幻读问题。
事实上,MVCC对于所有的当前读都无效,比如事务A修改数据之后,事务B去Update对应的数据,Update语句筛选条件针对的是数据库中当前的数据,而不是快照数据。
我是御狐神,欢迎大家关注我的微信公众号:wzm2zsd
MySQL之MVCC与幻读 正确的理解MySQL的MVCC及实现原理 MySQL数据库事务各隔离级别加锁情况--read commITted && MVCC
以上是脚本宝典为你收集整理的InnoDB学习(五)之MVCC多版本并发控制全部内容,希望文章能够帮你解决InnoDB学习(五)之MVCC多版本并发控制所遇到的问题。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。