多版本并发控制MVCC

多版本控制MVCC

调度

冲突串行化

在中 ACID 的 C ,指的是数据库中记录或映射的数据与真实世界中要一致,通俗一点说就是准确。事务的原子性,隔离性和持久性最终的目的都是为了数据的准确。

串行化是保证数据库一致性的最高级别。但是为了获得更好的数据库性能,多个事务需要同时执行。这时数据库的调度显得尤为重要,也就是数据库的并发控制。

串行化是指同一个事务的操作被同时调度,同一时间内只执行同一事务内的操作。

  • 如果两个事务间存在对同一数据项的写操作(write),那么认为这俩事务是冲突的,那么不同的调度顺序就会产生不同的结果,那么必然存在着不一致的情况存在。
  • 事务间不冲突的操作,其先后执行的顺序是不重要的。
  • 如果调度S通过调整不冲突的操作成为 S’,那么调度S和 S’冲突等价的。
  • 如果调度 S’是串行化的,那么认为调度 S 是冲突可串行化
  • 通俗说来,就是调度 S 经过一系列等价的调整事务执行次序,最终得到一个串行化的调度。因为串行化调度是能够保证数据一致性的,因此 S 也是能够保证数据一致性的。

可恢复调度

如果,事务 T_1依赖 T_2,读取 T_2中的未提交到数据(未提交读),T_1提交之后,T_2还处于活跃状态。此时如果 T_2出现了问题需要回滚到最初状态,可是 T_1已经提交,所以此次的调度属于不可恢复调度。那么只要依赖的事务后于被依赖的事务提交,即可做到可回复调度

无级联调度

虽然事务做到了可回复,但是如果发生了回滚,相关依赖的事务都要进行回滚,如果依赖事务很对便会产生大量的级联回滚。因此需要去掉事务间的依赖关系,也就是无级联调度。如果事务 T_1 读取先前 T_2说写的数据,那么只要 T_2 在 T_1读取操作前提交,这样 T_1和 T_2之间便没有了依赖。

隔离级别

  • 串行化 事务最准确的执行调度,完全的隔离。
  • 可重复读 读取已经提交的数据,同时不受事务期间其它事务提交的影响,因此容易在提交事务时候与最新的数据库状态有冲突,于会产生幻读。
  • 已提交读 只能读取已经提交的数据,收到其它事务提交的影响,可能同一事务读取多次同一个数据,等到不一样的结果。
  • 未提交读 事务更改的数据,在其它事务中都可以看到,事务之间几乎没有隔离。 级别越高性能越低,准确性越高。准确高性能是每个数据库设计者的努力方向。MVCC 就是其中一个。

时间戳协议

当事务并发执行,数据发生冲突的情况下,如何解决冲突是这些协议的根本目的。

首先我们引入以下相关的变量

  1. 对于事务 T,我们把一个唯一的时间戳与它联系起来,记为 TS(T)。
  2. 对于数据项 Q,W-ts(Q)表示执行 Write 操作的最大时间戳。R-ts(Q)表示执行 Read操作的最大时间戳。

时间戳顺序协议(Timestamp-ordering protocol)

  1. 事务T发出read(Q)操作,如果 TS(T)<W-ts(Q)说明 T 想读的 Q 已经被覆盖,那么 read 操作应该被拒绝,T 事务进行回滚操作。
  2. 事务T发出read(Q)操作,如果 TS(T)>=W-ts(Q),执行 read 操作,并且将 R-ts(Q)设为 TS(T)
  3. 事务T发出write(Q)操作,如果 TS(T)<R-ts(Q) 或者 TS(T)<W-ts(Q) 说明 T 产生的 Q 值已经过时,操作终止,T 事务回滚
  4. 其它情况,事务T发出write(Q)操作,执行 write 操作,并且将W-ts(Q)设为 TS(T) 该协议保证了无死锁,同时确保了冲突可串行化,因为冲突都会按照时间戳的顺序,采用新值,回滚旧值来处理。但是这里存在一个问题,如果有大量短事务与一个长事务冲突,可能会导致长事务反复重启,从而产生事务饥饿的现象。

MVCC

MVCC 是由时间戳顺序协议(Timestamp-ordering protocol)扩展而来。目前绝大多数并发控制机制,要么延迟操作要么终止操作来确保可串行性。如果系统仍然保留历史旧值,就可以避免这样的问题。 因此MVCC的核心是非常朴素的。每个满足条件的 write(Q) 操作会创建一个Q 的新版本,而 read(Q)操作会选择一个合适的 Q 的版本进行读取。 在介绍 MVCC 之前,需要引入几个变量

  1. 数据项 Q,有<Q_1,Q_2…Q_k>与之关联。
  2. 每个版本 Q 中包含具体内容 Content,W-ts(Q_k)是Q_k 的创建时间戳。R-ts(Q)是 Q_k的最大读取时间戳。

下面是 MVCC 的运作办法

  1. 如何事务 T 发出 read(Q),那么返回相应 TS(T)的 Q版本 的content
  2. 如果事务 T 发出 write(Q),如果 TS(T)>=W-ts(Q_k),而TS(T)<R-ts(Q) ,数据写入太迟,回滚T
  3. 如果事务 T 发出 write(Q),如果 TS(T)==W-ts(Q_k),覆盖 Q_k
  4. 如果事务 T 发出 write(Q),如果 TS(T)>=W-ts(Q_k),而TS(T)>R-ts(Q) ,创建新的版本
  5. Q_k和 Q_j 两个版本的 W-ts 都小于系统最老事务时间戳,那么 Q_k和 Q_j 中的老版本即可删除。这一点很好理解,系统最老的事务都比 Q_k和 Q_j 年轻,那么 Q_k和 Q_j 中年老的就永远不会被读到了。

MVCC 的读取请求永远不会失败,这一点对于实际生产环境中读取远大于写入到数据库非常重要。但是这里也有一个缺点,每次读取都会更新 R-ts因此会产生至少两次磁盘操作。

最近的文章

Flink笔记--窗口

Flink学习笔记窗口WindowsWindows是处理无限数据流的核心。Windows将数据流切割成为有限大小的”数据块”,并在”数据块”上执行相应的计算。窗口的生命周期属于窗口的第一个元素到达时窗口会被创建,而到达加上指定延迟的最后期限时窗口被移除。每个窗口会有一个 Tigger 触发器和一个处理数据的函数。触发器决定着什么时候可以调用处理数据的函数。除此以外还有一个移除器,决定将哪些数据移除。分组与无分组窗口使用keyBy(…) 会将流逻辑分成多个流。多个流之间是可以并行计算的。同一...…

继续阅读
更早的文章

Spark 整体感知

为了能够深入使用 Spark ,那么必须对 Spark 有更为深入的理解。整体的结构的把握会显得非常重要。整体结构在脑海中成型后,别如同脑海中有了一个 Spark 的地图,后续深入了解的每个模块都能在整体上找到对应的位置,开发对应功能也会更加了然于胸。网上的结构图非常丰富,但是我觉得都太过抽象,缺少一些细节。我这里尝试做的就是帮助 Spark 新手读者能构建一个 Spark 的直观感受,同时也是我自己知识的一个整理。小Demo下面是一段简单的 demo 程序。首先从数据源中取数,这里是从文...…

继续阅读