极客时间 分布式数据库 笔记02

极客时间 分布式数据库 笔记02

极客时间 分布式数据库 笔记02

极客时间 分布式数据库 笔记02

极客时间 分布式数据库 笔记02

极客时间 分布式数据库 笔记02

极客时间 分布式数据库 笔记02

极客时间 分布式数据库 笔记02

10 | 原子性:如何打破事务高延迟的魔咒?

极客时间 分布式数据库 笔记02

极客时间 分布式数据库 笔记02

极客时间 分布式数据库 笔记02

极客时间 分布式数据库 笔记02

极客时间 分布式数据库 笔记02

极客时间 分布式数据库 笔记02

并行执行的过程是这样的。

准备阶段的操作,在 CockroachDB 中被称为意向写。这个并行执行就是在执行意向写的同时,就写入事务标志,当然这个时候不能确定事务是否提交成功的,所以要引入一个新的状态“Staging”,表示事务正在进行。那么这个记录事务状态的落盘操作和意向写大致是同步发生的,所以只有一轮共识算法开销

 

   而客户端得到所有意向写的成功反馈后,可以直接返回调用方事务提交成功。注意!这个地方就是关键了,客户端只在当前进程内判断事务提交成功后,不维护事务状态,而直接返回调用方;事后由异步线程根据事务表中的线索,再次确认事务的状态,并落盘维护状态记录。这样事务操作中就减少了一轮共识算法开销。

 

11|隔离性:读写冲突时,快照是最好的办法吗?

   所以说,用锁解决读写冲突问题,带来的事务阻塞开销还是不小的。相比之下,用 MVCC 来解决读写冲突,就不存在阻塞问题,要优雅得多了

极客时间 分布式数据库 笔记02

极客时间 分布式数据库 笔记02

极客时间 分布式数据库 笔记02

极客时间 分布式数据库 笔记02

极客时间 分布式数据库 笔记02

极客时间 分布式数据库 笔记02

极客时间 分布式数据库 笔记02

从上面的例子可以发现,RC 与 RR 的区别在于 RC 下每个 SQL 语句会有一个自己的快照,所以看到的数据库是不同的,而 RR 下,所有 SQL 语句使用同一个快照,所以会看到同样的数据库。

为了提升效率,快照不是单纯的事务 ID 列表,它会统计最小活动事务 ID,还有最大已提交事务 ID。这样,多数事务 ID 通过比较边界值就能被快速排除掉,如果事务 ID 恰好在边界范围内,再进一步查找是否与活跃事务 ID 匹配。

 

CockroachDB 没有使用快照,不是因为没有全局事务列表,而是因为它的隔离级别目标不是 RR,而是 SSI,也就是可串行化

CockroachDB 的每个事务都有一个优先级,出现事务冲突时会比较两个事务的优先级,高优先级的事务继续执行,低优先级的事务则被重启。而被重启事务的优先级也会提升,避免总是在竞争中失败,最终被“饿死”

 

12 | 隔离性:看不见的读写冲突,要怎么处理?

还有一种看不见的读写冲突,它是由于时间的不确定性造成的,更加隐蔽,处理起来也更复杂

极客时间 分布式数据库 笔记02

只有避免时间窗口出现重叠。 那么如何避免重叠呢?

答案是等待。“waiting out the uncertainty”,用等待来消除不确定性

写等待的处理方式是这样的。事务 Ta 在获得“提交时间戳”S 后,再等待ɛ时间后才写盘并提交事务。真正的提交时间是晚于“提交时间戳”的,中间这段时间就是等待。这样 Tb 事务启动后,能够得到的最早时间 TT2.earliet 肯定不会早于 S 时刻,所以 Tb 就一定能够读取到 Ta。这样就符合线性一致性的要求了。

综上,事务获得“提交时间戳”后必须等待ɛ时间才能写入磁盘,即 commit-wait。

  2PC 的第一阶段是预备阶段,每个参与者都会获取一个“预备时间戳”,与数据一起写入日志。第二阶段,协调节点写入日志时需要一个“提交时间戳”,而它必须要大于任何参与者的“预备时间戳”。所以,协调节点调用 TT.now() 函数后,要取该时间区间的 lastest 值(记为 s),而且 s 必须大于所有参与者的“预备时间戳”,作为“提交时间戳”

针对同一个数据项,事务 T8 和 T9 分别对进行写入和读取操作。T8 在绝对时间 100ms 的时候,调用 TT.now() 函数,得到一个时间区间[99,103],选择最大值 103 作为提交时间戳,而后等待 8 毫秒(即 2ɛ)后提交。

这样,无论如何 T9 事务启动时间都晚于 T8 的“提交时间戳”,也就能读取到 T8 的更新

 

写等待模式下,所有包含写操作的事务都受到影响,要延后提交;而读等待只在特殊条件下才被触发,影响的范围要小得多。

极客时间 分布式数据库 笔记02

极客时间 分布式数据库 笔记02

在这个过程中,可以看到读等待的两个特点:一是偶发,只有当读操作与已提交事务间隔小于设置的时间误差时才会发生;二是等待时间的更长,因为事务在重启后可能落入下一个不确定时间窗口,所以也许需要经过多次重启。

 

13 | 隔离性:为什么使用乐观协议的分布式数据库越来越少?

   乐观协议就是直接提交,遇到冲突就回滚;悲观协议就是在真正提交事务前,先尝试对需要修改的资源上锁,只有在确保事务一定能够执行成功后,才开始提交。

极客时间 分布式数据库 笔记02

极客时间 分布式数据库 笔记02

极客时间 分布式数据库 笔记02

极客时间 分布式数据库 笔记02

    当然,为了避免这种情况的出现,TiDB 的乐观锁约定了事务的长度,默认单个事务包含的 SQL 语句不超过 5000 条。但这种限制其实是一个消极的处理方式,毕竟业务需求是真实存在的,如果数据库不支持,就必须通过应用层编码去解决了

极客时间 分布式数据库 笔记02

   另外,悲观锁还触发了一个变化。TiDB 原有的事务模型并不是一个交互事务,它会把所有的写 SQL 都攒在一起,在 commit 阶段一起提交,所以有很大的并行度,锁的时间较短,死锁的概率也就较低。因为增加了悲观锁的加锁动作,变回了一个可交互事务,TiDB 还要增加一个死锁检测机制。

14 | 隔离性:实现悲观协议,除了锁还有别的办法吗?

极客时间 分布式数据库 笔记02

极客时间 分布式数据库 笔记02

极客时间 分布式数据库 笔记02

强两阶段*协议(Strong Strict 2PL,SS2PL),事务一直持有已经获得的所有锁,包括写锁和读锁,直到事务终止。SS2PL 与 S2PL 差别只在于一直持有的锁的类型,所以它们的图形是相同的。

极客时间 分布式数据库 笔记02

极客时间 分布式数据库 笔记02

极客时间 分布式数据库 笔记02