[TOC]

链接:https://www.jianshu.com/p/dd3724fc0f66 作者:Aaron_Swartz

转载:https://www.cnblogs.com/YFYkuner/p/5178684.html

概念

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

如果有人从数据库中读数据的同时,有另外的人写入数据,有可能读数据的人会看到『半写』或者不一致的数据。有很多种方法来解决这个问题,叫做并发控制方法。最简单的方法,通过加锁,让所有的读者等待写者工作完成,但是这样效率会很差。MVCC 使用了一种不同的手段,每个连接到数据库的读者,在某个瞬间看到的是数据库的一个快照,写者写操作造成的变化在写操作完成之前(或者数据库事务提交之前)对于其他的读者来说是不可见的。

当一个 MVCC 数据库需要更一个一条数据记录的时候,它不会直接用新数据覆盖旧数据,而是将旧数据标记为过时(obsolete)并在别处增加新版本的数据。这样就会有存储多个版本的数据,但是只有一个是最新的。这种方式允许读者读取在他读之前已经存在的数据,即使这些在读的过程中半路被别人修改、删除了,也对先前正在读的用户没有影响。这种多版本的方式避免了填充删除操作在内存和磁盘存储结构造成的空洞的开销,但是需要系统周期性整理(sweep through)以真实删除老的、过时的数据。对于面向文档的数据库(Document-oriented database,也即半结构化数据库)来说,这种方式允许系统将整个文档写到磁盘的一块连续区域上,当需要更新的时候,直接重写一个版本,而不是对文档的某些比特位、分片切除,或者维护一个链式的、非连续的数据库结构。

MVCC 提供了时点(point in time)一致性视图。MVCC 并发控制下的读事务一般使用时间戳或者事务 ID去标记当前读的数据库的状态(版本),读取这个版本的数据。读、写事务相互隔离,不需要加锁。读写并存的时候,写操作会根据目前数据库的状态,创建一个新版本,并发的读则依旧访问旧版本的数据。

一句话讲,MVCC就是用 同一份数据临时保留多版本的方式 的方式,实现并发控制。

这里留意到 MVCC 关键的两个点:

  1. 在读写并发的过程中如何实现多版本;
  2. 在读写并发之后,如何实现旧版本的删除(毕竟很多时候只需要一份最新版的数据就够了);

MVCC是一种多版本并发控制机制

多版本控制: 指的是一种提高并发的技术。最早的数据库系统,只有读读之间可以并发,读写,写读,写写都要阻塞。引入多版本之后,只有写写之间相互阻塞,其他三种操作都可以并行,这样大幅度提高了InnoDB的并发度。在内部实现中,与Postgres在数据行上实现多版本不同,InnoDB是在undolog中实现的,通过undolog可以找回数据的历史版本。找回的数据历史版本可以提供给用户读(按照隔离级别的定义,有些读请求只能看到比较老的数据版本),也可以在回滚的时候覆盖数据页上的数据。在InnoDB内部中,会记录一个全局的活跃读写事务数组,其主要用来判断事务的可见性。 MVCC是一种多版本并发控制机制。

实现

MVCC 使用时间戳(TS)、递增的事务 ID(T)实现事务一致性。

MVCC 通过维护多版本数据,保证一个读事务永远不会被阻塞。对象 P 维护有多个版本,每个版本会有一个读时间戳(Read TimeStamp, RTS)和 写时间戳(Write TimeStamp, WTS),事务 Ti 读对象 P 的最新版本,该版本早于事务 Ti 的读时间戳 RTS(Ti)。

事务 Ti 要对 P 执行写操作,如果有其他事务 Tk 同时对 P 操作,则 RTS(Ti)必须要早于 RTS(Tk),即有 RTS(Ti) < RTS(Tk),这样对 Ti 对 P 的写操作才能完成。一般地,如果其他事务拥有 P 的一个更早的读时间戳的情况下,写操作是不能完成的。打个比方就是在存储前面有一道线,只有等你前面的人的完成了他们的事务,你的修改事务才可以提交完成。

重复说一下:每个对象 P 有一个时间戳 TS,如果事务 Ti 想要对 P 执行写操作,(写要先读)事务的读时间戳是 RTS(Ti),如果有其他事务拥有一个比较早的时间戳,有 TS(P) < RTS(Ti),这时事务 Ti 会退出并重新开始。否则,事务 Ti 创建一个 P 的新版本,并设置新版本 P 的时间戳,似的 TS = TS(Ti)。

**MVCC 系统明显的缺点是会存储多个版本数据的冗余开销。**但同时,读操作永不会被阻塞,这对那些以读操作为主的数据库来说非常重要。MVCC 实现了真的快照隔离(snapshot isolation),然后其他的并发控制方法要么是不完整的快照隔离方式,要么需要较高的性能损耗。

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

MVCC是为了解决什么问题?

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

*   MVCC是被Mysql中 `事务型存储引擎InnoDB` 所支持的;
*   **应对高并发事务, MVCC比`单纯的加锁`更高效**;
*   MVCC只在 `READ COMMITTED`  `REPEATABLE READ` 两个隔离级别下工作;
*   MVCC可以使用 `乐观(optimistic)锁`  `悲观(pessimistic)锁`来实现;
*   各数据库中MVCC实现并不统一
*   但是书中提到 "InnoDB的MVCC是通过在每行记录后面保存**两个隐藏的列**来实现的"(网上也有很多此类观点), 但其实并不准确, 可以参考[MySQL官方文档](https://dev.mysql.com/doc/refman/5.7/en/innodb-multi-versioning.html), 可以看到, InnoDB存储引擎在数据库每行数据的后面添加了**三个字段**, 不是两个!!

MVCC 具体实现分析

下面,我们通过InnoDB的MVCC实现来分析MVCC使怎样进行并发控制的.

InnoDB的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的,这两个列,分别保存了这个行的创建时间,一个保存的是行的删除时间。这里存储的并不是实际的时间值,而是系统版本号(可以理解为事务的ID),每开始一个新的事务,系统版本号就会自动递增,事务开始时刻的系统版本号会作为事务的ID.下面看一下在REPEATABLE READ隔离级别下,MVCC具体是如何操作的.

而InnoDB实现MVCC的方式是:

  • 事务以排他锁的形式修改原始数据

  • 把修改前的数据存放于undo log,通过回滚指针与主数据关联

  • 修改成功(commit)啥都不做,失败则恢复undo log中的数据(rollback)

  • 乐观锁和悲观锁

  • 1 悲观锁
        1排它锁当事务在操作数据时把这部分数据进行锁定直到操作完毕后再解锁其他事务操作才可操作该部分数据这将防止其他进程读取或修改表中的数据
        2实现大多数情况下依靠数据库的锁机制实现
      实现方式
      一般使用 select ...for update 对所选择的数据进行加锁处理例如select * from account where name=Max for update 这条sql 语句锁定了account 表中所有符合检索条件name=Max”)的记录本次事务提交之前事务提交时会释放事务过程中的锁),外界无法修改这些记录
    
    2 乐观锁
      实现方式
        1如果有人在你之前更新了你的更新应当是被拒绝的可以让用户重新操作
        2实现大多数基于数据版本Version记录机制实现
      具体可通过给表加一个版本号或时间戳字段实现当读取数据时将version字段的值一同读出数据每更新一次对此version值加一当我们提交更新的时候判断当前版本信息与第一次取出来的版本值大小如果数据库表当前版本号与第一次取出来的version值相等则予以更新否则认为是过期数据拒绝更新让用户重新操作
    

「真诚赞赏,手留余香」

真诚赞赏,手留余香

使用微信扫描二维码完成支付