暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

CockroachDB的MVCC实现机制

NoSQL漫谈 2018-09-26
1251

Cockroach的事务机制主要是参考了《分布式数据库MVCC技术探秘 (1)》一文所讲到的Serial Snapshot Isolation机制 , 在实际实现中存在一点出入:


Serial Snapshot Isolation是在事务提交阶段做冲突检测,而Cockroach是在事务执行阶段做冲突检测

我们先来看看Cockroach事务机制中的几个重要概念:

1.事务ID:每个事务都有一个事务ID,作为事务的唯一标识。

2.事务的状态:pending, aborted, committed。

3.事务的隔离级别:SI、SSI 。

4.事务表(Transaction Table): 记录整个系统中所有事务的状态,以及处于committed状态事务提交的时间。

事务运行流程中的几个主要状态:

1.start

每个事务开始的时候会分配一个initi timestamp和一个优先级别。但是一个事务并不是拥有唯一的 initial timestamp 和优先级别

2.restart

事务冲突时,冲突的一个事务需要restart,restart时,把当前事务abort,重新启动一个新的事务,此时事务的initial timestamp与优先级别可能会发生变化。但是对用户看来,就是一个事务

3.candidate commit

事务除了在开始会分配一个initial timestamp,还会分配一个 candidate commit timestamp ,初始值为initial timestamp,它在事务执行过程中可以被修改(只能变大)。当事务执行写操作的时候,会在key值中加入intend flag 标签和candidate commit timestamp,表示事务会在candidate commit timestamp 这个时间提交。

4.commit

一个事务的是否真正commit 在与此事务是否在transaction table中将事务的状态更新为commited。此时会去掉key值中的intend flag,用于判断是否提交,但是transaction table里面的状态才是真正衡量一个事务是否提交的标志.

Key的格式:

未提交事务的数据Key格式: 

key + intend + candidate commit timestamp

已提交数据的Key的内存格式:

key + latest read timestamp

持久化后,每个Key/Value按照事务提交时间记录的多个版本格式:

key + version1( timestamp)

key+ version2( timestamp)

Cockroach 事务实现原理


如果每个事务串行执行,事务的处理就会变的极为简单:加全局锁,执行事务。但是这种的方式效率过于底下,甚至导致不相干的事务都只能串行处理。为了提升性能,需要尽可能提升事务处理的并发度,同时又能满足事务的隔离级别要求。事务并发处理会碰到如下三种场景:

  • 读写冲突

  • 写写冲突

  • 写读冲突

读写冲突


读写冲突顾名思义,对某一行或者某几行进行读取时,存在另外一个事务同时对这一行或者这几行执行写操作。

在数据读时,内存的Key会记录一个latest read timestamp,这是最后一次读操作对应事务的initial timestamp(事务的开始时间),该事务每执行一个读操作,都会使用initial timestamp 与Key值中的latest read timestamp 进行比较,把该Key值latest read timestamp 更新为max(initial timestamp, original latest read timestamp)。

在存在读写冲突时,需要关注如下几点:

1. 读取数据的Key(内存状态)上是否存在intend标记

2. 写事务的暂定提交时间(candidate commit timestamp)和读事务的initial timestamp的先后

3. 写事务在事务表(transaction table)是否处于提交状态

4. 写事务的隔离级别

5. 写事务的优先级别

处理流程如下:


写写冲突

写写冲突顾名思义,当前事务A对某一行或者某几行执行写操作时,存在另外一个事务B同时对这一行或者这几行执行写操作。

那么在事务运行的过程中,需要关注如下几点:

1. 事务A待写入数据的Key(内存状态)上是否存在intend标记

2. 事务B的在数据表中的状态是否提交

3. 事务A和事务B的优先级别

4. 事务A写入数据的Key在存储在磁盘的最新版本(timestamp)

处理流程如下


写读冲突


写读冲突顾名思义,当前事务A对某一行或者某几行进行写操作时,存在另外一个事务B同时对这一行或者这几行执行读操作。

相对于读写冲突和写写冲突,写读冲突处理起来要简单很多,那么在事务运行的过程中,需要关心的几点有:

1. 事务待写入数据的Key(内存状态)的last read timestamp

2. 事务待写入数据的Key(内存状态)的candidate commit timestamp


处理流程如下:


理论分析


开篇讲到Serial Snapshot Isolation是在事务提交阶段做冲突检测,而Cockroach是在事务执行阶段做冲突检测。我们还需要从理论上分析上面的冲突处理流程是否符合MVCC的基础规则。

再回顾一下MVCC事务机制:

Snapshot Isolation 中描述的write snapshot isolation涉及两个规则:

规则一: RW-spatial Overlap : txnj writes into row r and txni

规则二:Ts(txni) < Tc(txnj) <Tc(txni)

Similarity to snapshot isolation ,write-snapshot isolation assigns unique start and commit timestamp to transactions and ensures that txni reads the latest version of data with commit timestamp phi < Ts(txni)

write snapshot isolation 为每个事务分配一个开始的时间戳,一个事务提交时间戳,保证每一个(读写)事务读到一行版本的提交时间戳要小于事务的开始时间

在上面冲突处理流程中可以看到Cockroach写到Transaction Table里面的事务提交时间并不是事务运行的结束的时间,而是把事务的提交时间提前。当 Ts(txni) == Tc(txni)的时候,没有任何一个事务txnj满足Ts(txni) < Tc(txnj) <Tc(txni)。举个例子:假设一个事务开始时,它的initial timestamp 与candidate commit timestamp 都是2,如果这个事务正常提交(不发生冲突),而且假设这个事务结束时的绝对时间为4,Cockroach在Transaction Table写入的事务是在timestamp = 2的时间点提交的。也就是说Cockroach里面记录事务提交时间要比事务真实运行时提交的时间要早。这样就可以满足上面的规则要求。

但是什么场景下允许这样做而不影响数据的一致性呢?关键点在于数据库运行期间,没有其他事务关心这个事务在什么时间提交。或者说在事务运行的时间区间内,该事务修改的行没有被处在这个时间区间内的snapshot 读到过,如果出现这样的事务,SSI事务要么把自己abort掉,要么把对方的事务abort掉。

简单描述就是:一个SSI事务如果提交成功,那么它的Ts与Tc是相等的(Ts相当于它的initial timestamp ,Tc是它的final commit timestamp)。在Cockroach的SSI的事务是不允许自己的candidate commit time 往后推,如果SSI事务能够提交成功,那么它的candidate commit time 是跟initial commit time相等的,write snapshot isolation规则二中就不可能有一个事务txnj 满足 Ts(txni) < Tc(txnj) <Tc(txni)。也就是说一个Cockroach的SSI事务提交的时候,不可能有其它事务修改了SSI事务读取的行。下篇文章将列举一些典型例子来进一步阐述该机制。

关于"NoSQL漫谈"


NoSQL主要泛指一些分布式的非关系型数据存储技术,这其实是一个非常广泛的定义,可以说涉及到分布式系统技术的方方面面。随着人工智能物联网大数据云计算以及区块链技术的不断普及,NoSQL技术将会发挥越来越大的价值。

请长按下面的二维码关注我们

更多NoSQL技术分享,敬请期待!

文章转载自NoSQL漫谈,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论