MVCC简单了解学习

MVCC多版本并发控制

什么是MVCC

MVCC全称为Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制得方法,一般用于对数据库库事务得并发访问出现得安全问题。

MVCC是为了解决什么问题?

在数据库一般存在三种冲突的场景

  • 读操作和读操作:不存在任何问题,也不需要并发控制

  • 写操作和读操作:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读

  • 写操作和写操作:有线程安全问题,可能会存在更新丢失问题,

我们都可以通过加锁的方式去解决这两种冲突,但是加锁无疑是会降低数据库的效率。因此为了提高效率,我们就需要使用MVCC来解决读写冲突,**(MVCC解决不了写写之间的冲突)**避免了加锁操作,降低了开销。

当前读、快照读

在学习MVCC之前我们需要了解当前读、快照读的区别。

  • 当前读

    像select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读,当前读就是指的是事务读取的是当前最新的数据,并且会进行加锁操作,不允许其他并发事务修改当前的记录。

  • 快照读

    不加锁的select 都是快照读,前提是隔离级别不是串行级别,否则快照读就会退化称为当前读。而MVCC就是基于快照都实现的。

MVCC的好处

就像上述说的,MVCC采用无锁的操作来解决读写之间的并发问题,会为每一个修改保存一个当前事务id,读操作只会读取该事务开始执行时**(select执行)**的快照。

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

MVCC的实现原理

它的实现原理主要是依赖记录中的 3个隐式字段、undo日志 、ReadView 来实现的。下面我们就开始说一下这几个概念

隐式字段

在数据库中一条数据记录,除了我们自己定义的字段,还存在着三个隐式字段

  • DB_TRX_ID:保存的是最近修改(插入)的事务ID,
  • DB_ROLL_PTR:回滚指针,指向这条记录的上一个版本地址
  • DB_ROW_ID:隐式自增ID,当数据表中没有主键,innodb会使用字段产生一个聚簇索引

image-20210824161111379

undo日志

当事务对数据库进行修改时,InnoDB会生成对应的undo log;如果事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。
当发生回滚时,InnoDB会根据undo log的内容做与之前相反的工作:每个insert,回滚时会执行delete

当我们在person表中插入一条了数据,如 {id= 1;name=Mike;age=18}的记录,此时的隐式主键为1,事务id为null,回滚指针为null

现在来了一个事务1对该记录的name做出了修改,改为Tom

  • 在事务1修改该行(记录)数据时,数据库会先对该行加排他锁

  • 然后把该行数据拷贝到undo log中,作为旧记录,既在undo log中有当前行的拷贝副本

  • 拷贝完毕后,修改该行name为Tom,并且修改隐藏字段的事务ID为当前事务1的ID, 我们默认从1开始,之后递增,回滚指针指向拷贝到undo log的副本记录,既表示我的上一个版本就是它

  • 事务提交后,释放锁

image-20210824164147709

ReadView

什么是ReadView

readview 即读视图,就是在事务在进行快照读的时候生成一个列表,它用来记录的是当前数据库中活跃的事务ID(未提交),这个id是自增的,即事务开启越晚的id就越大。

作用

通过这个列表来判断记录的某个版本是否对当前事务可见。假设当前列表里的事务id为[80,100]。

  • 如果我当前查询的数据的事务id为50,显然比最小的事务id:80要小。说明id为50的这个事务已经被提交了,那么对于当前查询的事务来说是可见的。
  • 如果我当前查询的数据的事务id为70,显然当前这个事务id在列表中,说明这个事务还未被提交了,那么对于当前查询的事务来说是不可见的,那么我就需要通过回滚指针找到上一个版本的数据,再次进行比较
  • 如果当前查询的数据的事务id为110,那比事务列表最大id100都大,那说明这个版本是在ReadView生成之后才发生的,所以不能被访问。

案例

比如此时有一个事务id为10的事务,修改了name,使得的name等于LInk,但是事务还没提交。则此时的版本链如下如所示

image-20210824165430073

其中白色的为事务未提交的,绿色的为事务已提交的。

  • 此时有一个事务开启查询id =1 的数据

    select * from person where id = 1;
    
  • 此时生成了一个快照,ReadView则为[5,10];

  • 此时就会顺着版本链开始寻找了,首先寻找到版本号为10的,发现在ReadView中,说明该版本对当前事务是不可见的,需要继续找下一条版本数据

  • 会找到版本号为5的数据,发现也在ReadView中了说明该版本对当前事务也是不可见的,需要继续找下一条版本数据

  • 此时会找到版本号为3的数据,并不在ReadView中,说明该版本事务已经提交了,对于当前是是可见的。

代码实现

建表

CREATE TABLE person(
	id INT(5),
	`name` VARCHAR(10),
	age INT(10)
)

INSERT INTO person (id,`name`,age) VALUES(1,"Mike",18)

开启事务3,并提交——》将Mike修改为Tom

START TRANSACTION
UPDATE person SET NAME = 'Tom' WHERE id = 1;
COMMIT

打开另一个会话窗口session2

START TRANSACTION
UPDATE person SET NAME = 'Jimmy' WHERE id = 1;

打开另一个会话窗口session3

START TRANSACTION
UPDATE person SET NAME = 'LInk' WHERE id = 1;

此时我们再次进行查询希望得到数据是{id=1;name=Tom;age=18}

image-20210824181145548

补充点:

  1. ReadView是在开启事务后执行的select语句是生成的,如果只开启了事务,并没有执行select语句,那么ReadView并没有生成
  2. 在RR和RC隔离级别下生成ReadView的方式也不是一致的。在RR隔离级别下,开启事务后,当执行select语句时,会生成ReadView,并==且在这个事务中此后的select语句就不再生成ReadView了。使用的都是相同的一个ReadView。==而在RC隔离级别下,开启事务后每执行一次select就会生成一个ReadView。这也就是RR级别下可以防止可重复读的原因了

知识有限,难免有错,欢迎指正

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值