任何数据库的主要要求之一就是实现可伸缩性。只有将争用(锁定)最小化(如果不能一起删除),才可以实现。由于读/写/更新/删除是数据库中发生的一些主要的频繁操作,因此对于这些操作并发进行而不被阻塞非常重要。为了实现这一目标,大多数主要数据库都采用了一种称为多版本并发控制的并发模型,该模型将争用降低到最低限度。
多版本并发控制(以下简称MVCC)是一种算法,可通过维护同一对象的多个版本来提供精细的并发控制,以使READ和WRITE操作不会冲突。这里的WRITE表示UPDATE和DELETE,因为无论如何新插入的记录都将按照隔离级别受到保护。每个WRITE操作都会生成对象的新版本,并且每个并发读取操作都会根据隔离级别读取对象的不同版本。由于读取和写入操作均在同一对象的不同版本上进行,因此这些操作都不需要完全锁定,因此两者都可以并发操作。争用仍然存在的唯一情况是当两个并发事务尝试写入同一记录时。
当前大多数主要数据库都支持MVCC。该算法的目的是维护同一对象的多个版本,因此MVCC的实现因数据库而异,仅在创建和维护多个版本方面有所不同。因此,相应的数据库操作和数据存储发生了变化。
公认的实现MVCC的方法是PostgreSQL和Firebird / Interbase使用的一种,而InnoDB和Oracle使用的另一种。在随后的章节中,我们将详细讨论如何在PostgreSQL和InnoDB中实现它。
为了支持多个版本,PostgreSQL维护每个对象的其他字段(PostgreSQL术语为Tuple),如下所述:
PostgreSQL将所有数据存储在称为HEAP的主存储中(页面的默认大小为8KB)。所有新元组都将xmin作为创建它的事务进行处理,而旧版本元组(已更新或删除)将分配给xmax。从旧版本元组到新版本始终存在链接。在隔离的情况下,较旧版本的元组可用于在回滚的情况下重新创建元组,并可通过READ语句读取较旧版本的元组。
考虑到表有两个元组,T1(值1)和T2(值2),可以在下面的3个步骤中演示新行的创建:

MVCC:在PostgreSQL中存储多个版本
如图所示,数据库中最初有两个元组,其值分别为1和2。
然后,在第二步中,将值2的行T2更新为值3。这时,将使用新值创建一个新版本,并将其存储为与现有元组相邻并将其存储在同一存储区域中。在此之前,较旧的版本将分配给xmax并指向最新版本的元组。
类似地,在第三步中,当删除具有值1的行T1时,将在同一位置虚拟删除现有行(即,它为当前事务分配了xmax)。没有为此创建新版本。
接下来,让我们看一下每个操作如何创建多个版本,以及如何保持事务隔离级别而不用一些实际示例进行锁定。在下面的所有示例中,默认情况下使用“ READ COMMITTED”隔离。
每次插入记录时,都会创建一个新的元组,并将其添加到属于相应表的页面之一中。

PostgreSQL并发INSERT操作
正如我们在这里看到的逐步:
PostgreSQL UPDATE不是“ IN-PLACE”更新,即它不会使用所需的新值来修改现有对象。相反,它将创建该对象的新版本。因此,UPDATE大致涉及以下步骤:
因此,即使许多记录保持不变,HEAP也会占用空间,就好像插入了多条记录一样。

PostgreSQL并发INSERT操作
正如我们在这里看到的逐步:
删除几乎与UPDATE操作类似,只是它不必添加新版本。只是将当前对象标记为DELETED,如UPDATE情况中所述。

PostgreSQL并发DELETE操作
如我们所见,没有任何操作可以直接删除对象的现有版本,并且在需要的地方都可以添加对象的其他版本。
现在,让我们看一下如何在具有多个版本的元组上执行SELECT查询:SELECT需要读取所有版本的元组,直到根据隔离级别找到合适的元组为止。假设有元组T1,它已更新并创建了新版本T1',并在更新时又创建了T1'':
如我们所见,它需要遍历元组的所有3个版本,以便找到合适的可见元组,直到过期的元组被垃圾收集器(VACUUM)删除为止。
为了支持多个版本,InnoDB为每行维护其他字段,如下所述:
与PostgreSQL一样,InnoDB在所有操作中也会创建该行的多个版本,但是旧版本的存储有所不同。
对于InnoDB,更改后的行的旧版本保存在单独的表空间/存储中(称为undo段)。因此,与PostgreSQL不同,InnoDB在主存储区中仅保留行的最新版本,而在undo段中保留较旧的行。还原段中的行版本用于回滚时的撤消操作,并根据隔离级别通过READ语句读取旧版本的行。
考虑到表有两行,T1(值1)和T2(值2),可以通过以下3个步骤来演示新行的创建:

MVCC:在InnoDB中存储多个版本
从图中可以看出,数据库中最初有两行,其值分别为1和2。
然后在第二阶段中,将值2的行T2更新为值3。这时,将使用新值创建新版本,并替换旧版本。在此之前,较旧的版本将存储在撤消段中(请注意,UNDO段版本仅具有增量值)。另外,请注意,回滚段中有一个从新版本到旧版本的指针。因此,与PostgreSQL不同,InnoDB更新是“ IN-PLACE”。
类似地,在第三步中,当删除具有值1的行T1时,则在主存储区域中虚拟删除了现有行(即,它只是在行中标记了一个特殊位),并在其中添加了与之对应的新版本。撤消段。同样,从主存储器到撤消段只有一个滚动指针。
从外部看,所有操作的行为均与PostgreSQL相同。只是多个版本的内部存储有所不同。
现在,让我们分析PostgreSQL和InnoDB在MVCC实现方面的主要区别是什么:
PostgreSQL MVCC没有什么缺点,特别是在工作负载频繁更新/删除的情况下,存储空间过大。因此,如果您决定使用PostgreSQL,则应该非常小心地配置VACUUM。
PostgreSQL社区也承认这是一个主要问题,他们已经开始研究基于UNDO的MVCC方法(临时名称为ZHEAP),我们可能会在以后的版本中看到同样的情况。
原文作者:;如有侵权,请联系管理员删除。