MVCC多版本控制太难懂了,看了不少文章资料终于弄清楚了一丢丢

MVCC

1.1 什么是MVCC

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

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

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

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

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

1.2 MVCC优缺点

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

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

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

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

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

1.2 MVCC具体实现

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

1.2.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.2.2 undo日志

包括以下两种:

  • insert undo log
    代表事务在insert新记录时产生的undo log,;
    只在事务回滚时需要,并且在事务提交后可以被立即丢弃
  • update undo log
    事务在进行update或delete时产生的undo log;
    不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除

1.2.3 read_view

read_view:每个事务在开始的时候都会根据当前系统的活跃事务链表创建一个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);
  1. 插入测试数据
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 :tid<tmin,此时事务2尚未commit,所以读取未被事务2修改的数据
事务2 :tid==当前事务id,所以读取到被修改后的数据
事务3 :tmin<tid<tmax,且tid在活跃事务列表中,所以顺着回滚指针跳转到上一行记录,即取出未修改的数据进行读取;

参考

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章