MVCC学习总结
Posted Fighter168
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MVCC学习总结相关的知识,希望对你有一定的参考价值。
好久没有写博客了,下周有两个重要的场合要应对,又得撸起学习袖子了,加油~
1、什么是MVCC
MVCC(Multi-Version Concurrency Control)即多版本并发控制。MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问
2、mysql怎么实现MVCC
nnoDB存储的最基本row中包含一些额外的存储信息 DATA_TRX_ID、DATA_ROLL_PTR、DB_ROW_ID、DELETE BIT。
DATA_TRX_ID标记了最新更新这条行记录的transaction id,每处理一个事务,其值自动+1
DATA_ROLL_PTR 指向当前记录项的rollback segment的undo log记录,找之前版本的数据就是通过这个指针
DB_ROW_ID,当由innodb自动产生聚集索引时,聚集索引包括这个DB_ROW_ID的值,否则聚集索引中不包括这个值,这个用于索引当中
DELETE BIT位用于标识该记录是否被删除,这里的不是真正的删除数据,而是标志出来的删除,真正意义的删除是在commit的时候。
2.1、初始插入数据行
F1~F6是某行列的名字,1~6是其对应的数据。后面三个隐含字段分别对应该行的事务号和回滚指针,假如这条数据是刚INSERT的,可以认为ID为1,其他两个字段为空
2.2、事务1更改该行的各字段的值
当事务1更改该行的值时,会进行如下操作:
用排他锁锁定该行
记录redo log
把该行修改前的值Copy到undo log,即上图中下面的行
修改当前行的值,填写事务编号,使回滚指针指向undo log中的修改前的行
2.3、事务2修改该行的值
与事务1相同,此时undo log中有两行记录,并且通过回滚指针连在一起。因此,如果undo log一直不删除,则会通过当前记录的回滚指针回溯到该行创建时的初始内容,所幸的是在Innodb中存在purge线程,它会查询那些比现在最老的活动事务还早的undo log,并删除它们,从而保证undo log文件不至于无限增长。
当事务正常提交时只需要更改事务状态为COMMIT即可,不需做其他额外的工作,而Rollback则稍微复杂点,需要根据当前回滚指针从undo log中找出事务修改前的版本并恢复。如果事务影响的行非常多,回滚则可能会变的效率不高,根据经验值没事务行数在1000~10000之间,Innodb效率还是非常高的。很显然,Innodb是一个COMMIT效率比Rollback高的存储引擎。
3、InnoDB实现的MVCC有何特殊性
上述更新前建立undo log,根据各种策略读取时非阻塞就是MVCC,undo log中的行就是MVCC中的多版本,这个可能与我们所理解的MVCC有较大的出入,一般我们认为MVCC有下面几个特点:
每行数据都存在一个版本,每次数据更新时都更新该版本
修改时Copy出当前版本随意修改,各个事务之间无干扰
保存时比较版本号,如果成功则commit并覆盖原记录;失败则放弃copy(rollback)
就是每行都有版本号,保存时根据版本号决定是否成功,听起来含有乐观锁的味道,而Innodb的实现方式是:
事务以排他锁的形式修改原始数据
把修改前的数据存放于undo log,通过回滚指针与主数据关联
修改成功(commit)啥都不做,失败则恢复undo log中的数据(rollback)
二者最本质的区别是,当修改数据时是否要排他锁定,如果锁定了还算不算是MVCC。
MVCC可以保证不阻塞地读到一致的数据。但是MVCC理论并没有对实现细节做约束,为此不同的数据库的语义有所不同,比如:
postgres 对写操作也是乐观并发控制;在表中保存同一行数据记录的多个不同版本,每次写操作都是创建,而回避更新; 在事务提交时,按版本号检查当前事务提交的数据是否存在写冲突,则抛异常告知用户,回滚事务;
innodb 则只对读无锁,写操作仍是上锁的悲观并发控制,这也意味着,innodb中只能见到因死锁和不变性约束而回滚,而见不到因为写冲突而回滚; 不像 postgres 那样对数据修改在表中创建新纪录,而是每行数据只在表中保留一份,在更新数据时上行锁,同时将旧版数据写入 undo log; 表和 undo log 中行数据都记录着事务ID,在检索时根据事务隔离级别去读取行数据。可见 MVCC中的写操作仍可以按悲观并发控制实现;
MVCC解决的问题是读写互相不阻塞的问题,每次更新都产生一个新的版本,读的话可以读历史版本。试想,如果一个数据只有一个版本,那么多个事务对这个数据进行读写是不是需要读写锁来保护?
一个读写事务在运行的过程中在访问数据之前先加读/写锁这种实现叫做悲观锁,悲观体现在先加锁,独占数据,防止别人加锁。
乐观锁呢,读写事务,在真正的提交之前,不加读/写锁,而是先看一下数据的版本/时间戳,等到真正提交的时候再看一下版本/时间戳,如果两次相同,说明别人期间没有对数据进行过修改,那么就可以放心提交。
乐观体现在,访问数据时不提前加锁。在资源冲突不激烈的场合,用乐观锁性能较好。
如果资源冲突严重,乐观锁的实现会导致事务提交的时候经常看到别人在他之前已经修改了数据,然后要进行回滚或者重试,还不如一上来就加锁。
4、快照读与当前读
快照读就是读取数据的时候会根据一定规则读取事务可见版本的数据(可能是过期的数据),不用加锁。
当前读, 读取的是最新版本, 并且对读取的记录加锁,保证其他事务不会再并发的修改这条记录,避免出现安全问题。 使用当前读的场景:
select…lock in share mode (共享读锁)
select…for update
update
delete
insert
使用快照读的场景:
单纯的select操作,不包括上述 select … lock in share mode、select … for update
通过举例来理解快照读与当前读吧:MySQL innoDB的RR隔离级别下,假设你开启了两个事务,分别是A和B,这里有个张user表,里面有四条数据。
CREATE TABLE user (
id int(11) NOT NULL,
name varchar(64) NOT NULL,
PRIMARY KEY (id),
KEY name (name) )
ENGINE=InnoDB;
insert into user values(0,"Jack"),(5,"Tom"), (10,"Jerry"),(15,"ZhangSan");
当你执行select *之后,在A与B事务中都会返回4条一样的数据,这是不用想的,RR隔离级别下当执行普通的select查询时,innodb默认会执行快照读,相当于就是给你目前的状态找了一张照片,以后执行select 的时候就会返回当前照片里面的数据,当其他事务提交了也对你不造成影响,和你没关系,这就实现了可重复读,那这个照片是什么时候生成的呢?
不是开启事务的时候,是当你第一次执行select的时候,也就是说,当A开启了事务,然后没有执行任何操作,这时候B insert了一条数据然后commit,这时候A在事务中执行select,那么就能看到有B在自己在事务中添加的那条数据…,在这之后无论再有其他事务commit都没有关系,因为照片已经生成了,而且不会再生成了,以后都会参考这张照片。
总结
所谓的MVCC(Multi-Version Concurrency Control 多版本并发控制)指的就是在使用读已提交(READ COMMITTD)、可重复读(REPEATABLE READ)这两种隔离级别的事务在执行普通的SELECT操作时访问记录的版本链的过程,这样子可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。
这两个隔离级别的一个很大不同就是:生成ReadView的时机不同,READ COMMITTD在每一次进行普通SELECT操作前都会生成一个ReadView,而REPEATABLE READ只在第一次进行普通SELECT操作前生成一个ReadView,数据的可重复读其实就是ReadView的重复使用。
InnoDB通过为每一行记录添加两个额外的隐藏的值来实现MVCC,这两个值一个记录这行 数据何时被创建,另外一个记录这行数据何时过期(或者被删除)。但是InnoDB并不存储这些事件发生时的实际时间,相反它只存储这些事件发生时的系统版本号。这是一个随着事务的创建而不断增长的数字。每个事务在事务开始时会记录它自己的系统版本号。每个查询必须去检查每行数据的版本号与事务的版本号是否相同。
这种额外的记录所带来的结果就是对于大多数查询来说根本就不需要获得一个锁。
他们只是简单地以最快的速度来读取数据,确保只选择符合条件的行。这个方案的缺点在于存储引擎必须为每一行存储更多的数据,做更多的检查工作,处理更多的善后操作。
使用MVCC多版本并发控制比锁定模型的主要优点是在MVCC里, 对检索(读)数据的锁要求与写数据的锁要求不冲突, 所以读不会阻塞写,而写也从不阻塞读。
在数据库里也有表和行级别的锁定机制, 用于给那些无法轻松接受 MVCC 行为的应用。 不过,恰当地使用 MVCC 总会提供比锁更好地性能。
5、思考
首先这里说明下mysql 的一些基础知识,简单复习下
5.1、mysql 默认隔离级别
mysql 的隔离级别有:读未提交,读已提交,可重复读,串行执行
mysql 的默认隔离级别是:可重复读,可重复读可以解决幻读
什么是幻读:这里简单的理解为一个事物中的两次查询操作,查询出来的结果不一样。
5.2、mysql执行CRUD各加什么锁
查询操作默认是不加索的,但是也可以通过for update 的方式加排它锁,可以通过lock in share mode 的方式加共享锁
insert 、update、delete 默认是排它锁。
参考资料
Mysql的共享锁和排他锁:https://wenku.baidu.com/view/87aa2edf971ea76e58fafab069dc5022aaea462b.html
大白话讲解脏写、脏读、不可重复读和幻读:https://zhuanlan.zhihu.com/p/150107974
MVCC简介:https://www.jianshu.com/p/ad43961f20c6
以上是关于MVCC学习总结的主要内容,如果未能解决你的问题,请参考以下文章