<导航

Mysql MVCC多版本并发控制

1、简介

  Multi-Version Concurrency Control,多版本并发控制,每次操作,copy一份所要改的数据作为副本,副本之间通过一个版本号字段区分,并将副本的版本号+1,如果是更新操作,数据在副本上修改完后,要更新时候查看原纪录的版本号是否是副本版本号-1,是,更新,否(说明有其他修改事务在这期间修改了数据,使其版本号更新了),失败,重新取数据重新更新;如果是读操作,则是根据隔离级别读取小于等于当前事务版本号数据库数据(并不更改版本号)。
  上面的逻辑只是粗略说明MVCC是一个怎么样的东西,实际各个存储引擎的实现并不一定就这样,事实上,MVCC只是一个标准说明,而没有说明具体的实现。

2、理论

高性能MySQL一书上说的MVCC实现方式:
  InnoDB的MVCC:通过给每条记录后面保存两个隐藏的字段来实现:一个是行的创建时间,一个是行的删除时间。当然,实际上存的不是时间值,而是系统版本号,每开始一个新的事务,系统版本号会自动递增。事务开始的版本号则是系统版本号,用来和查询到的每行记录的版本号作比较。下面是隔离级别为Repeatable read下MVCC具体操作:

SELECT
Innodb检查没行数据,确保他们符合两个标准:
  1、InnoDB只查找版本早于当前事务版本的数据行(也就是数据行的版本必须小于等于事务的版本),这确保当前事务读取的行都是事务之前已经存在的,或者是由当前事务创建或修改的行。
  2、行的删除操作的版本一定是未定义的或者大于当前事务的版本号。确保了当前事务开始之前,行没有被删除(2)。
  符合了以上两点则返回查询结果。
INSERT
  InnoDB新插入的行以当前系统版本号为行版本号。
DELETE
  InnoDB为删除的每一行保存当前系统版本号作为删除标识。
UPDATE
  InnoDB复制了一行。这个新行的版本号使用了系统版本号。它也把系统版本号作为了删除行的版本作为删除标识。
  上面最重要的是系统版本号一个概念,系统版本号其实就是这个表从创建到此时所经历过的事务次数,开始为0。注意系统版本号不是行版本号,表的每个行的版本号一般是不一样的,因为不是每次操作都是针对整个表的。有了这个概念下面就好理解了:
  一个事务过程:

 1 //注意,以下是针对隔离级别为Repeatable read
 2 0.事务开始,以下步骤数据操作并没有写到数据库中
 3 1.系统版本号+1
 4 2.事务版本号=系统版本号
 5 3.进行操作
 6     (1)读操作:遍历数据库表,查找版本号<=事务版本号的行(这里包括了复制的行,等于是
 7 因为可能这个事务对数据作了修改或增加),还有,这个时候可能有其他事务对数据做了修改,但
 8 修改会改变行版本号(增加),选取<=,所以是看不见的(隔离级别控制),而若这个时候有其
 9 他事务有增加新数据,同样版本号>当前系统版本号,所以不会出现幻读。
10     (2)插入操作:增加一个新行,行版本号=系统版本号
11     (3)删除操作:将该行的删除版本号(注意不是行版本号)=系统版本号,注意,这个时候并
12 没有物理删除,只是做了一个标记(如果最后不删除,或则哪怕删除,会存在类似undo.log文件
13 中,用来回滚)
14     (4)更新操作:复制所更新行记录,将原行进行删除操作,新行版本号=系统版本号
15 4.提交事务(Commit),试图更新数据:
16 17 (1)对比数据行的副本与原数据,若副本的行版本号=原数据行版本号,更新成功,否则失败,回滚操作。 18 (2)若有行数据的删除版本=当前系统版本号,删除数据行。 19 5.操作成功,更新数据。

  再说下其他两个隔离级别:Read commited和Serializable,这两个级别下,MVCC是没用的,因为Read commited总是读取最新的数据,而不是符合当前事务版本的数据行,而Serializable呢,则会使对当前所有读取的行加锁,所以MVCC完全没用。

3、实现

innodb MVCC主要是为Repeatable-Read事务隔离级别做的。在此隔离级别下,A、B客户端所示的数据相互隔离,互相更新不可见

了解innodb的行结构、Read-View的结构对于理解innodb mvcc的实现由重要意义

innodb存储的最基本row中包含一些额外的存储信息 DATA_TRX_ID,DATA_ROLL_PTR,DB_ROW_ID,DELETE BIT

  • 6字节的DATA_TRX_ID 标记了最新更新这条行记录的transaction id,每处理一个事务,其值自动+1

  • 7字节的DATA_ROLL_PTR 指向当前记录项的rollback segment的undo log记录,找之前版本的数据就是通过这个指针

  • 6字节的DB_ROW_ID,当由innodb自动产生聚集索引时,聚集索引包括这个DB_ROW_ID的值,否则聚集索引中不包括这个值.,这个用于索引当中
  • DELETE BIT位用于标识该记录是否被删除,这里的不是真正的删除数据,而是标志出来的删除。真正意义的删除是在commit的时候

4、MVCC与乐观锁

搜索查了一下二者的关系,发现很多说法,众说纷纭,这里我个人觉得这两者的关系是:
  二者均是概念上的思想,并不是实现,而二者也不是等于或包含被包含关系,因为,乐观锁可以通过MVCC这种思路实现,也可以通过其他方法实现,比如CAS;但是MVCC本身只是想实现非阻塞的读,可以认为MVCC是行级锁的一个变种,很多情况避免了加锁操作,但不是没有加锁,写操作是有加锁的,只是加的是必要的行,事实上MVCC的实现有乐观并发控制,也有悲观并发控制的。

5、总结  

  总的来说,MVCC就是用空间换时间,用复制一行来操作,而不是对行加锁,减少了加锁操作,之后查看原行是否在这期间有被更新过,若无操作成功,若有操作失败,回滚重新操作。适用于单行操作,如果过多多行更新操作的话,失败率很高,性能效率很低。
  MVCC在大多数情况下代替了行锁,实现了对读的非阻塞,读不加锁,读写不冲突。缺点是每行记录都需要额外的存储空间,需要做更多的行维护和检查工作。

 

 

参考文章:

https://blog.csdn.net/a327369238/article/details/52808865

https://blog.csdn.net/chen77716/article/details/6742128

https://www.jianshu.com/p/a3d49f7507ff

posted @ 2019-02-06 17:34  字节悦动  阅读(398)  评论(0编辑  收藏  举报