union store 数据一致性以及约束检查

2019-01-26

在 TiDB 里面,事务的所有操作都会先临时缓存在本地内存,直到事务提交的时候,通过 2PC 将事务的所有修改整体提交。

这样会遇到一个问题,后面的操作需要 “看见” 前面的操作的修改。比如插入 a,再读取 a,这时需要读到刚刚插入的 a。由于前面的插入并没有真正写下去,我们读的时候需要做一个融合:优先读本地缓存,读不到的情况下,再穿透缓存去读下面的存储。把这些东西封装起来,让调用者只管当它是一个 store,我们把这个封装的代码叫 union store。

union store 概念上尽管很简单,但是实际的代码还是挺乱的。中间穿插着 dirty table,binlog,事务状态等等东西,维护起来容易出错。我前面写过一篇编写安全的代码,是思考理想的情况下,应该怎么样让代码更健壮。但是现实与理想相去甚远。这里很容易弄坏数据,出现数据不一致,对于数据库,数据错了就是灾难。然而没人能保证代码永无 bug。所以这次换一个角度:如何让 bug 不产生破坏性影响,让系统更健壮。

这应该算工程手段吧。可以先解释所谓的工程手段。

做个类比,我要写很多数据到磁盘,也不确定会不会中间有数据出错。于是可以写数据的时候,生成一份检验信息,把检验信息和数据一起写出去,然后验证。该过程并消灭掉中间写数据出错的可能性,但是捕获到出错的情况了。

再做个类比,就像写一个内存分配器,想保证代码完全没有 bug 并不容易。那么让定位 bug 更容易一些呢?可以把每次分配操作,在哪分配,分配了多少空间,全部记录下来,然后去重放操作。能复现就好查问题了。

回到正题,先分析下导致数据写坏的 bug 都是如何产生,比如写了数据没写索引;比如该缓存没有刷,上一次操作的缓存留到下一次了;或者错误处理没做好,有一半成功一半失败,导致部分残留了;比如更新的时候,读出错了,接着用读到的错误数据去写...

union store 看到的东西,都不是真实的,而是缓存融合真实存储后的一个视图。缓存融合的过程中,可能 bug 导致数据有问题。

约束检查是个这样的想法:所有操作过程中,都会产生一些假设。 我们把操作的假设 (contract) 全记录下来,提交缓存数据时,把 contract 一起提交下去验证。 就相当于,代码无 bug 的情况下,这些 contract 应该是满足的。 如果代码有 bug,就有可能捕获了。

举个具体点的例子,更新一条记录,会先读后写,更新数据和更新索引。那么,这个操作的 contract 就包含,老的索引数据存在,老的数据存在。如果是唯一索引,那么生成的 contract 还包括索引数据是不存在的。

union storecontract