# 3. \[WIP]分布式事务@原理

描述一下我们的基本矛盾 , 因为数据越来越大, 所以一定会将数据切成好多分片, 存放在不同的实力上. 而同样的一个分片, 也会被存好几份, 其中的某一份被称作是主分片, 剩下来的几份就是副本, 一方面这些副本用来容灾, 另一方面副本也\可以作为只读, 用来提升读的效率.

## 如何论证: 事务的先后关系?

![](https://github.com/XiaohanLiang/review/tree/afc839b3ce8d6f8e21cfbd123ac7bcda27d9f5ed/Users/xiaohanliang/Desktop/%E6%88%AA%E5%B1%8F2021-08-13%20%E4%B8%8B%E5%8D%884.13.54.png)

假设我们有三个服务器 **\[Server1(比客观时间快5ms) / Server2(比客观时间慢4ms) / Server3(比客观时间慢2ms)]**, 在上面发生了两个事务 $T\_1$ (先发生) / $T\_2$ (后发生), 那么在数据库记录下这两个事务的时间戳的时候, ${S}\_2$应该大于${S}\_1$. **因此, 我们只需要证明: 已知** **$T\_1$的提交时间早于$T\_2$的开始时间, 求证** **$S\_1** **< S\_2$.** 为了能满足这个性质, 要求:

* $S\_i$应当大&#x4E8E;**\[所有参与者的] 本地开始时间**, 并且需要大于协调者的 `TT.Now().Latest`: Start原则
* 真正提交的时间必须在 $S\_i$ 之后,  即真正提交时间大于记录下的时间戳: Cond Wait原则

如果使用这个规则:

* $T\_1$ 开始的时候:&#x20;
  * Server1的本地时间是15ms, Server2(协调者)的本地时间是7ms, 按照Start原则$S\_1$会被选择为 max(7+7, 15) = 15
  * $T\_1$的真正执行时间要在15之后我们假设是17ms
* $T\_2$开始的时候:
  * Server2(协调者)的本地时间是12ms, Server3的本地时间是13ms, 按照Start原则$S\_2$会被选择为 max(12+7, 13) = 19
  * $T\_2$的真正执行时间要在19之后我们假设是21ms
* 结论:
  * 即使很快的就把事务做完了,  但为了协调时间,  我们故意把真正的提交时间滞后了
  * 但这样能做到事务记录时间上的有序化

如果不使用这个规则:

* Server1的本地时间是15ms, Server2(协调者)的本地时间是7ms,  直接选择15ms作为T1的执行时间S1
* Server2(协调者)的本地时间是12ms, Server3的本地时间是13ms, 直接选择13ms作为T2的执行时间S2
* 即使我们都知道Server2开始的更晚, **但记录下的时间, 却是Server2更早**, mess

## 两个原则怎么就解决了这个问题?

1. 已知客观时间上有:  $&#x74;*{abs}(T\_1^{commit}) <  t*{abs}(T\_2^{start})$&#x20;
2. 由于我们进行了CondWait, 所以记录下的时间戳 $S*1 < t*{abs}(T\_1^{commit}) $
3. **而时间戳 $S*****2$ 一定大于$t*****{abs}(T\_2^{start})$, 因为很显然只有$T\_2$开始了之后, 才能提交产生$S\_2$**
4. 因此 $ S*1 < t*{abs}(T*1^{commit}) <  t*{abs}(T\_2^{start}) < S\_2 $

第三点的解释:

* 时间戳$S*2$的选择:  **TrueTime区间的最大值 TT.Now().Latest**, 因此我们能做到S2大于 $t*{abs}(T\_2^{start})$
* 事实上我们不仅选择了协调者本身的 TT.Now().Latest,甚至考虑到所有参与者开始时间, 请注意S2的选择虽然是提交时间, 但我们为了保证大于T2的开始时间,  因此Start原则里max的是开始时间. 所以我们一定能得到 $ t\_{abs}(T\_2^{commit}) \le S\_2$ .&#x20;
* 此时根据传递性,  $ &#x74;*{abs}(T\_2^{start}) \lt t*{abs}(T\_2^{commit}) \le S\_2$

## 一些思考

我们在最上面的图中可以看到, 事实上我们已经把所有事务, 在协调者身上串行化(Linearized) 处理了, 这意味着如果两个事务涉及修改到同一条数据, 那么这两个事务一定得是串行的, 相对的, 如果两个事务并不涉及同一条事务, 那么他们可以并行, 因为Merge的时候并不会出问题. 另外因为我们要等两个 ε , 一个是选择的提交时间TT.Now().Latest, 另一个是TT.After($S\_i$)的, 因此等待成本还是挺大的. 根据论文里的数据, 正常来说一个 ε 有90%概率会在1ms之内. 串行的吞吐量是每秒125个.

但这事实上给我们一种可能, 就是集群中的所有节点都可以参与写操作. 这是一个很不一样的设计.

## Reference

* [Spanner的TrueTime与分布式事务](https://www.cnblogs.com/FateTHarlaown/p/12306894.html)
* [关于Spanner中的TrueTime和Linearizability](https://zhuanlan.zhihu.com/p/35473260)
* [计算机的时钟（四）：TrueTime](https://yang.observer/2020/11/02/true-time/)
