# `MVCC`

__*MVCC*__

```
- Transaction 1:
  Begin（start_ts = 100）
    < 1,Tom > -> < 1,Jack >
    < 2,Andy > -> < 2, Candy > 
  Commit;（commit_ts = 110）

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

解决上述问题的方法叫做多版本并发控制（`Multi-Version Concurrency Control`, 以下简称 `MVCC`）。

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

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

下面我们假设一个场景:

目前有两个事务, 事务 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 = 115）
< 1,Jack > -> < 1,Tim >
< 4,Tony > -> < 4, Jerry >
```

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

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