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

![left](./student-guide/diagram/PercolatorTiKV03_zh_001_20220511.png)

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

注:假设 key = 1,key = 2 分别在不同 TiKV 的 region 上。

首先,事务开始的时候,我们从 PD 中拿到了一个 startTS,假设为 100,我们进入 Percolator 的第一个阶段 PreWrite,这个时候,由于没有 commit,所以需要写入 Lock 和 Default 两个列族（CF）即可,从图中可以看出,在写入 Default 时候,我们将 key 加上了时间戳（startTS）, 两个修改的 Lock 列族（CF）的 value 也不太一样,主要包括 lock type 和 startTS 等。

如下图:

```
+****************************************************************************+
|                                              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 中拿到一个 commitTS,假设为 110,之后会在 Write 列族（CF）中写入,key 加上时间戳（commit_ts）, value 就是这个事务的 startTS, 在 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 模型,实际的实现还会比较复杂,我们也忽略了一些细节,如果要详细了解,可以参考 分布式事务课程 中的内容。