数据库事务( 三 ) 什么是MVCC

1.6.什么是 MVCC

1.6.1.什么是 MVCC

多版本并发控制(MVCC = Multi-Version Concurrency Control),是一种用来解决读 - 写冲突的无锁并发控制。

可以简单地认为:MVCC就是行级锁的一个变种(升级版)。在表锁中我们读写是阻塞的,基于提升并发性能的考虑,MVCC一般读写是不阻塞的(很多情况下避免了加锁的操作)。

可以简单的理解为:对数据库的任何修改的提交都不会直接覆盖之前的数据,而是产生一个新的版本与老版本共存,使得读取时可以完全不加锁。就是为事务分配单向增长的时间戳,为每个修改保存一个版本。版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照(复制了一份数据)。这样在读操作不用阻塞写操作,写操作不用阻塞读操作的同时,避免了脏读和不可重复读

1.6.2.MVCC 可以为数据库解决什么问题

在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能。

同时还可以解决脏读、幻读、不可重复读等事务隔离问题,但不能解决更新丢失问题。

1.6.3.说说 MVCC 的实现原理

MVCC 的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突,

它的实现原理主要是依赖记录中的 3 个隐式字段、undo log 日志、Read View 来实现的。

详细说明:

1.6.3.1.3个隐式字段

InnoDB 中 MVCC 的实现方式为:每一行记录都有两个隐藏列:DATA_TRX_ID、DATA_ROLL_PTR(如果没有主键,则还会多一个隐藏的主键列)。

DATA_TRX_ID : 记录最近更新这条行记录的事务 ID,大小为 6 个字节

DATA_ROLL_PTR : 表示指向该行回滚段(rollback segment)的指针,大小为 7 个字节,InnoDB 便是通过这个指针找到之前版本的数据。该行记录上所有旧版本,在 undo 中都通过链表的形式组织。

DB_ROW_ID : 行标识(隐藏单调自增 ID),大小为 6 字节,如果表没有主键,InnoDB 会自动生成一个隐藏主键,因此会出现这个列。另外,每条记录的头信息(record header)里都有一个专门的 bit(deleted_flag)来表示当前记录是否已经被删除。
在这里插入图片描述

F1~F3是某行列的名字,1~3是其对应的数据。后面三个隐含字段分别对应该行的事务号和回滚指针,

假如这条数据是刚INSERT的,可以认为ID为1,其他两个字段为空
在这里插入图片描述

1.6.3.2.如何组织 Undo Log 链

事务1

当事务1更改该行的值时,会进行如下操作:

  • 用排他锁锁定该行
  • 记录redo log
  • 把该行修改前的值Copy到undo log,
  • 修改当前行的值,填写事务编号,使回滚指针指向undo log中的修改前的行

在这里插入图片描述

事务2

与事务1相同,此时undo log中有两行记录,并且通过回滚指针连在一起。因此,如果undo log一直不删除,则会通过当前记录的回滚指针回溯到该行创建时的初始内容,所幸的是在Innodb中存在purge线程,它会查询那些比现在最老的活动事务还早的undo log,并删除它们,从而保证undo log文件不至于无限增长。

在这里插入图片描述

当事务正常提交时只需要更改事务状态为COMMIT即可,不需做其他额外的工作,而Rollback则稍微复杂点,需要根据当前回滚指针从undo log中找出事务修改前的版本并恢复。如果事务影响的行非常多,回滚则可能会变的效率不高,根据经验值设事务行数在1000~10000之间,Innodb效率还是非常高的。很显然,Innodb是一个COMMIT效率比Rollback高的存储引擎。

1.6.3.3.快照读和当前读

快照读: 读取的是记录数据的可见版本(有旧的版本)。不加锁,普通的select语句都是快照读,如:

select * from user where id = 1;

当前读:读取的是记录数据的最新版本,显式加锁的都是当前读

select * from user where id = 1 for update;
select * from user where id = 1 lock in share mode;
1.6.3.4.什么是ReadView 呢?

ReadView 就是事务进行快照读操作的时候生产的读视图 (Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的 ID (当每个事务开启时,都会被分配一个 ID , 这个 ID 是递增的,所以最新的事务,ID 值越大)

当我们用select读取数据时,这一时刻的数据会有很多个版本,但我们并不知道读取哪个版本,这时就靠readview来对我们进行读取版本的限制,通过readview我们才知道自己能够读取哪个版本。

在一个ReadView 快照中主要包括以下这些字段:

m_ids: 表示在生成ReadView 时, 当前系统中活跃的读写事务的事务ID 列表; 活跃的事务就是指还没有commit的事务。

**min_trx_id:**表示在生成ReadView 时, 当前系统中活跃的读写事务的事务ID 列表中最小值;

**max_trx_id:**表示在生成ReadView 时, 系统应该分配给下一个事务的 事务ID 值; 例如m_ids中的事务id为(1,2,3),那么下一个应该分配的事务id就是4,max_trx_id就是4。

creator_trx_id: 表示生成当胶ReadView 的事务的事务ID; 执行select读这个操作的事务的 事务ID

** ReadView 如何判断版本链中的哪个版本可用呢?(重点!)

TRX_ID 表示要读取的 事务ID

TRX_ID == creator_trx_id : 可以访问这个版本

如果要读取的事务id等于进行读操作的 事务ID,说明是我读取我自己创建的记录

TRX_ID < min_trx_id : 可以访问这个版本

如果要读取的 事务ID 小于最小的活跃 事务ID,说明要读取的事务已经提交,那么可以读取。

TRX_ID > max_trx_id : 不可以访问这个版本

如果要读取的 事务ID 大于max_trx_id,说明该id已经不在该readview版本链中了,故无法访问。

min_trx_id <= TRX_ID <= max_trx_id : 如果 TRX_ID 在m_ids中是不可以访问这个版本, 反之则可以

m_ids中存储的是活跃 事务ID ,如果要读取的 事务ID 不在活跃列表,那么就可以读取,反之不行。

1.6.4.MVCC如何实现RC和RR的隔离级别

(1)Read Committed(可读已提交) 的隔离级别下,每个快照读都会生成并获取最新的ReadView 。

(2)Repeatable Read(可重复读) 的隔离级别下,只有在同一个事务的第一个快照读才会创建ReadView ,之后的每次快照读都使用的同一个readview,所以每次的查询结果都是一样的。

1.6.5.幻读问题

快照读:通过MVCC,RR的隔离级别解决了幻读问题,因为每次使用的都是同一个ReadView 。
当前读:通过next-key锁(行锁+gap锁),RR隔离级别并不能解决幻读问题。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值