跳转至

乐观并发控制

1.简介

乐观并发控制(Optimstic Concurrency Control)假设事务的竞争、数据交集与冲突较少,在这种时候若对数据进行所有权争夺,则有来自于锁的额外开销。因此,OCC使用的思想是每个事务保存一份数据的本地副本,在提交的时候才对数据进行冲突校验。

OCC包括三个阶段

  • Read Phase:在基于事务的本地空间对数据进行读写
  • Validation Phase:对数据进行冲突校验
  • Write Phase:对数据进行原子性更新

其中事务会在Validation被分配时间戳,该时间戳用于在数据库中维护数据对象的最后版本。

2.Read Phase

该阶段做的事情十分简单,对读写数据保存到自己的一个副本中,然后在自己的副本中对数据进行修改,因此事务和事务之间具有隔离型。

每个事务都会维护Read set和Write set,保存他们对数据的读写情况。

3.Validation Phase

为了能够检测数据的冲突性,数据库会在上层拥有一个全局的视角,能都看到所有正在运行的事务以及他们的Read set和Write set。

事务需要Commit的时候要进行冲突校验,本质就是插入这个事务后,是不是满足Conflict Serializable的。可以粗略的分成两大类:

  • Backward Validation:检查之前已经Commit的事务有没有对本事务造成影响。如T1先于T2读取数据x,但是T2修改了x并先于T1提交,此时T1获得的x的副本数据就是旧数据。
  • Forward Validation:检查该事务是否对之后的事务造成影响。

我们常采用单向检测,即Forward Validation。我们假设Ti<Tj,即Ti先于Tj提交(进入Validation Phase),则以下的情况是合法的:

  • COMMIT(Ti)<BEGIN(Tj):Ti和Tj不相交
  • WriteSet(Ti)∩ReadSet(Tj)=∅:Tj没有读到Ti修改前的值
  • WriteSet(Ti)∩ReadSet(Tj)=∅ && WriteSet(Ti)∩WriteSet(Tj)=∅

4.Write Phase

无论是否采用锁进行逻辑数据的并发保护,数据库的底层数据结构仍是需要利用锁进行并发保护的,这也是需要原子性更新数据的原因。

曾经提到过,在数据库中有两种锁,一种是保护逻辑数据的locks,另一种是保护底层数据结构的latches。对于OCC来说,即使并发事务的Read set和Write set是不相交的,他们在更新时抢夺底层数据结构的latches仍然存在竞争问题。

5.Partition-base T/O

需要争抢latch的原因是因为事务的并发,而如果给执行的事务都赋予时间戳,并根据时间戳进行顺序执行的话,就不需要latch了,但这样系统的速度取决于单核性能。

在此基础上,再对数据进行分区,进行intra-parallel从而安全地提高运行速度。这称之为horizontal partitions(shards,即分片)。

若不同的事务修改不同分区的数据的话,我们希望这些事务是可以并发执行的。因此,我们仍然要保证基于分区的latch,在每一个事务要执行的时候,事务需要推理并获取所有需要的分区的latch,才能去对应的分区读写数据。如果事务无法获取所有需要的分区的latch,则放弃并重启。

问题主要有,事务需要能够推断出数据所在的分区。其次,该做法在事务都只访问少数分区时才有好的性能表现,若许多事务都需要访问大量分区,那么获取多个分区的latch不仅比获取一个大的latch消耗更大,也会阻塞很多其他事务。