# MVCC（A）

+ 事务一：
```
Begin（start_ts = 100）
 < 1，Tom > -> < 1，Jack >
< 2，Andy > -> < 2, Candy > 
Commit;（commit_ts = 110）
```
+ 事务二：
```
Begin（start_ts = 115）
 < 1，Jack > -> < 1，Tim >
< 4，Tony > -> < 4, Jerry > 
```
-------------------------------------------------------------
从分布式事务的实现中，我们了解到，如果某行数据正在事务中（未提交），那么对于它的读取会返回错误。也就是说写操作不仅会阻塞写操作，也会阻塞读操作，虽然在理论上没有问题，但是对于高并发的系统来说，如果某行数据正在事务中，那么对于这行数据的操作马上会变为串行，效率非常低。

解决上述问题的方法叫做多版本并发控制（Multi-Version Concurrency Control，以下简称 MVCC）。像 Oracle，MySQL，PostgreSQ 等数据库也是采用这个方法。下面我们一起来看看 TiKV 是如何使用 MVCC 的。

在 MVCC 技术中，每当想要更改或者删除某个数据对象时，数据库不会在原地去删除或者修改这个已有的数据对象本身，而是创建一个该数据对象的新的版本，这样的话同时并发的读取操作仍旧可以读取老版本的数据，而写操作就可以同时进行。这个模式的好处在于，可以让读取操作不再阻塞，事实上根本就不需要锁。 

在分布式事务部分，我们已经看到了，对于数据的修改或者删除，TiKV 实际上只进行追加操作（PUT，DELETE），而不是去修改或者删除原有的数据。所以，在 TiKV 中我们是有之前版本的数据的，接下来就是如何找到合适的版本数据的问题了。

下面我们假设一个场景：
目前有 2 个事务，事务 1 已经提交，其 start_ts 为 100，commit_ts 为 110；事务 2 已经开始，但是，没有提交，其 start_ts 为 105。

事务一：
```
Begin（start_ts = 100）
 < 1，Tom > -> < 1，Jack >
< 2，Andy > -> < 2, Candy > 
Commit;（commit_ts = 110）
```
事务二：
```
Begin（start_ts = 105）
 < 1，Jack > -> < 1，Tim >
< 4，Tony > -> < 4, Jerry > 
```

此时，我们在 STO 为 120 的时候，启动一个查询，查询 key = 1，key = 2， key = 4 的值。根据常识，如果没有 MVCC 机制，那么 key = 1 和 key = 4 是返回错误或者被阻塞的，因为其正在事务二中，而 key = 2 可以查询出结果，value = Candy，因为事务一已经完成。

但是，如果有了 MVCC 机制，我们知道，在 STO 为 120 时的读取，不仅可以读取出 < 2, Candy > ，还可以读出  < 1，Jack > 和 < 4，Tony > ，因为事务一没有提交，我们可以读取提交之前的值。