MVCC多版本控制太难懂了


写在最前

以下是我参考过的觉得写的不错的文章:

《MVCC多版本控制》:一个旺财和小强对数据操作的故事,有趣易懂
《MVCC多版本并发控制》:较详细的解释,不难理解
《MVCC多版本并发控制机制》:从增删改查方面简单讲解
《MySQL中InnoDB的多版本并发控制(MVCC)》:通过表格讲解示例,容易理解
《InnoDB存储引擎MVCC的工作原理》:直接从客户端分析
《select for update引发死锁分析》:详细事例分析
《MySQL-InnoDB-MVCC多版本并发控制》:主要注重概念讲解,属于笔记类

下图是我自己关于mvcc的一个测试:
在事务2对数据修改后,三个事务分别对数据查询得到的输出结果,只有事务2能得到修改后的数据,这里就是由于mvcc多版本并发控制得到的结果。
在这里插入图片描述
好的,进入正题。


MVCC

1.1 什么是MVCC

MVCC,全称 Multi_Version Concurrency Control ,即 多版本并发控制

它是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存

  • 相对于传统的基于锁的并发控制主要特点是即使有读写冲突时,也能做到不加锁,非阻塞并发读;
    这种特性对于读多写少的场景,提高了数据库并发性能,因此大部分关系型数据库(如MySQL,Oracle,PostgreSQL)都实现了MVCC。

  • 锁机制可以控制并发操作,但是其系统开销较大,而MVCC可以在大多数情况下代替行级锁,使用MVCC,能降低其系统开销

  • MVCC是通过保存数据在某个时间点的快照来实现的。MVCC并没有一个统一的实现标准,不同存储引擎的MVCC实现是不同的,典型的有乐观并发控制悲观并发控制

1.2 MVCC优缺点

多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。

所以MVCC可以为数据库解决以下问题:

  • 在并发读写数据库时,MVCC在大多数情况下代替了行锁,实现了对读的非阻塞,读不加锁,读写不冲突,提高了数据库并发读写的性能

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

缺点是每行记录都需要额外的存储空间,需要做更多的行维护和检查工作。

脏读:在一个事务中读取到另一个事务没有提交的数据; 不可重复读:在一个事务中,两次查询的结果不一致(针对的update操作); 虚读(幻读):在一个事务中,两次查询的结果不一致(针对的insert操作)。

1.3 MVCC具体实现

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

1.3.1 隐式字段

每行记录除了我们自定义的字段外,还有数据库隐式定义的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID三个字段

  • 6字节的事务ID(DB_TRX_ID)字段:
    记录创建这条记录/最后一次修改该记录的事务ID
  • 7字节的回滚指针(DB_ROLL_PTR)字段:
    写入回滚段(rollback segment)的 undo log record (撤销日志记录)
    如果一行记录被更新, 则 undo log record 包含 ‘重建该行记录被更新之前内容’ 所必须的信息。
  • 6字节的行标识 DB_ROW_ID字段:
    插入新行而单调增加
    当由innodb自动产生聚集索引时,聚集索引会包括这个行ID的值,否则这个行ID不会出现在任何索引中。
  • 如果我们的表中没有主键或合适的唯一索引, 也就是无法生成聚簇索引的时候, InnoDB会帮我们自动生成聚集索引, 但聚簇索引会使用DB_ROW_ID的值来作为主键; 如果我们有自己的主键或者合适的唯一索引, 那么聚簇索引中也就不会包含 DB_ROW_ID。
  • 此外,删除在内部被视为更新,在该更新中,行中的删除flag隐藏字段被设置为将其标记为已删除。
1.3.2 undo日志

包括以下两种:

  • insert undo log
    代表事务在insert新记录时产生的undo log,;
    只在事务回滚时需要,并且在事务提交后可以被立即丢弃
  • update undo log
    事务在进行update或delete时产生的undo log;
    不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除
1.3.3 read_view
  • 每个事务在开始的时候都会根据当前系统的活跃事务链表创建一个;
  • 记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)。

对于read view快照的生成时机, 也非常关键, 正是因为生成时机的不同, 造成了RC,RR两种隔离级别的不同可见性;

  • 在innodb中(默认repeatable read级别), 事务在begin/start transaction之后的第一条select读操作后, 会创建一个快照(read view), 将当前系统中活跃的其他事务记录记录起来;
  • 在innodb中(默认repeatable committed级别), 事务中每条select语句都会创建一个快照(read view);

在read view中有一个可见性比较算法

首先了解read_view数据结构分为三个部分:
(1)当前活跃的事务列表
(2)Tmin,活跃事务的最小值
(3)Tmax,系统中最大事务ID(不管事务是否提交)加上1

结合上述结构的了解再看以下关于其算法的流程图:
在这里插入图片描述

2.1 测试示例

光看流程图可能还是不能完全理解,直接动手自己测试看看更加加深印象。

2.1.1 准备数据
  1. 创建测试表test_mvcc
    mysql> create table test_mvcc( name varchar(8),age int);
    
  2. 插入测试数据
    insert into table test_mvcc values('wj',23);
    insert into table test_mvcc values('wj2',24);
    insert into table test_mvcc values('wj3',25);
    
2.1.2 测试验证

开了三个窗口连接MySQL进行测试:

  1. 窗口一 未作任何操作;
  2. 窗口二 update修改了数据,但未commit;
  3. 窗口三 select数据。
    在这里插入图片描述
2.2.3 分析

可以从图中直观看到以下信息 :

  1. Trx id 均为76332(未有事务commit数据);
  2. 第一个事务 query id 为 71,第二个事务 query id 为 74,第三个事务 query id 为 74;
  3. 只有update的事务2能看到更新的数据,其他事务不可见。

可见性比较算法进行分析的话可以得到以下结果:

1)事务1 :tid<tmin,此时事务2尚未commit,所以读取未被事务2修改的数据;
2)事务2 :tid==当前事务id,所以读取到被修改后的数据;
3)事务3 :tmin<tid<tmax,且tid在活跃事务列表中,所以顺着回滚指针跳转到上一行记录,即取出未修改的数据进行读取。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值