# 分布式事务 (继续...)

![`smart`](PercolatorTiKV03_aa_002_20220511.png)

-------------------------------------------------------------
假设我们要完成的事务如下:
```
Begin
 < 1,Tom > -> < 1,Jack >
< 2,Andy > -> < 2, Candy > 
Commit;
```

假设 `key = 1, key = 2` 分别在不同的 `TiKV`节点的 `Region` 中。

首先, 事务开始的时候, 我们从 `PD` 中拿到了一个 `start_ts`, 假设为 100。然后进入 `Percolator` 的第一个阶段，即 `PreWrite`。此时, 由于尚未提交事务, 所以只需要写入 `Lock CF` 和 `Default CF` 即可, 从图中可以看出, 在写入 `Default CF` 的时候, 我们对 `key` 添加了时间戳（`start_ts`)。两个 `Lock CF` 的 `value` 有所不同, 主要包括 `lock_type`、`user_key` 和 `start_ts`。

如下图:
```
+****************************************************************************+
|                                              Key = 1                       |
+***********************+*********************************+******************+
|   Default CF      |            Lock CF             ｜      Write CF         |
+***********************+*******+**************************+*******+*********+
|   key  |  value   |  key |       value             |   key   |   value     |
+***********************+*******+********************************************+
| 1_100  |   Jack   |   1  |  W, primary key,100...  |         |             |
+***********************+*******+**************************+*****************+

+****************************************************************************+
|                                              Key = 2                       |
+***********************+*********************************+******************+
|        Default CF          |               Lock CF        ｜   Write CF     |
+***********************+*******+**************************+*******+*********+
|   key        |  value      |    key  |         value      |   key |  value |
+***********************+*******+********************************************+
|   2_100      |   Candy     |     2   |  W, @key = 1,00... |       |        |
+***********************+*******+**************************+*****************+
```

(`W:`表示写入, `D:`表示删除)

这里有个点很重要, 也就是事务中修改 `key = 1` 和 `key = 2`,在 `Lock CF`中, 我们只将第一个修改也就是 `key = 1` 作为 `primary key`, 而 `key = 2` 的锁是指向 `key = 1` 的。（即, `Lock CF` 中的 `value`）

同时,我们也注意到 `Default CF`, 无论是修改还是删除数据, 都是新插入键值对, 而不是去修改或者删除已经存储的数据, 这点在后面介绍 `MVCC` 技术的时候, 还会介绍到。

其次, 接下来我们进入 `Percolator` 的第二个阶段 `commit`, 这个时候会从 `PD` 中拿到一个 `commit_ts`, 假设为 110, 之后会在 `Write CF` 中写入, `key` 加上时间戳(`commit_ts`), `value` 就是这个事务的 `start_ts`, 在 `Lock CF`记录 `primary key` 被删除。

如下图:
```
+************************************************************************************+
|                                              Key = 1                               |
+***********************+*********************************+**************************+
|        Default CF         |               Lock CF              ｜     Write CF     |
+***********************+*******+**************************+*******+*****************+
|   key       |  value      |    key  |         value            |   key   |   value |
+***********************+*******+****************************************************+
| 1_100       |   Jack      |     1   |  W, primary key,100...   |         |         |
+***********************+*******+**************************+*************************+
|             |             |     1   |  D,  primary key,100...  |         |         |
+***********************+*******+**************************+*************************+

+**************************************************************************************+
|                                              Key = 2                                 |
+***********************+*********************************+****************************+
|        Default CF         |               Lock CF          ｜           Write CF      |
+***********************+*******+**************************+*******+*******************+
|   key        |  value     |    key  |        value         |   key   |   value       |
+***********************+*******+******************************************************+
| 2_100        |   Candy    |     2   |  W, @key = 1,00...   |         |               |
+***********************+*******+**************************+***************************+
|              |            |     2   |  D,  @key = 1,00...  |  2_110  | start_ts=100  |
+***********************+*******+**************************+***************************+
```

(`W:`表示写入, `D:`表示删除)

`commit` 阶段完成后, 当我们要读取数据时, 会发现无论是 `key = 1` 还是 `key = 2`, 我们只要查找对应的 `Lock CF` 中是否有锁即可, 如果没有锁或者锁被删除(标志为 `D`), 则可以读取, 否则读取就会失败。

最后, 我们发现, `Percolator` 的 `commit` 阶段我们只需要删除一个 `key = 1` 的 `primary key` 就可以了, 因为 `key = 2` 的 `Lock CF` 中存储的是指向 `key = 1` 的 `primary key` 的指针。那么假设, 如果在 `commit` 阶段, 由于错误,只删除了 `key = 1`, 没有来得及删除 `key = 2`, 在恢复以后, 当读取到 `key = 2` 时,发现 `Lock CF` 上有锁（由于错误没有被删除）指向 `key = 1`, 而 `key = 1` 对应的锁已经被删除, 我们就知道在错误前没有来得及更新（删除） `key = 2` 的锁, 但实际上事务已经提交成功了。于是, 就解决了之前提到的分布式事务的问题。

我们这里只是简单介绍了 `Percolator` 模型, 实际的实现还会比较复杂。
