黄东旭解析 TiDB 的核心优势
448
2024-04-10
上一篇文章大致讲了 TIKV 的分布式事务基本原理,还有几个分布式事务的接口大概逻辑:
https://tidb.net/blog/e5e5ae0d
上个文章中,对于 Prewrite 接口遇到的异常情况,只举了两个非常典型的场景。
本篇文章着重更详细的介绍 Prewrite 接口内部逻辑,看一下对于各种各样的异常场景是如何处理的。
下面的场景样例均以下面的例子为基础:
Let’s see the example from the paper of Percolator. Assume we are writing two rows in a single transaction. At first, the data looks like this:
This table shows Bob and Joe’s balance. Now Bob wants to transfer his $7 to Joe’s account.
Get the start_ts of the transaction. In our example, it’s 7.
为了分布式事务的正确性,在执行 Prewrite 前,需要对 Prewrite 涉及的 KEY 都要进行如下检查:
检查在 lock_cf 中没有记录,也就是没有锁
检查在 write_cf 中没有大于等于当前事务 start_ts 的记录
例如下面的例子 (start_ts=7) 就可以通过前置检查:
可以看到存在两个 KEY,一个是 Bob,一个是 Joe。两个 KEY 的 lock_cf 都是空的,同时 write_cf 的最新记录是 6,小于 start_ts(7)
例如下面的例子 (start_ts=7) ,前置检查就会失败,因为 Bob 的 lock_cf 存在一个 ts 为 9 的 primary lock:
例如下面的例子 (start_ts=7) ,前置检查就会失败,因为 Bob 的 write_cf 存在一个 commit_ts=9 的记录:
前置检查通过后,我们开始进行真正的 Prewrite 操作。
作为2PC 的第一阶段,预提交。目的是将事务涉及的多个 KEY-VALUE 写入 default_cf,同时将在 lock_cf 上加锁
将 KEY-VALUE 写入 default_cf
将 lock 信息写入 lock_cf 上加锁
Prewrite 操作前,存储的事务状态为:
对 Bob 和 Joe 进行 Prewrite 操作后,存储的事务状态为:
值得注意的是,tidb 指定 Bob 是 primary key,Bob 写入的 lock 是 primary lock。指定 Joe 是 secondary key,Joe 写入的 lock 是 secondary lock。
通过 Joe 的 secondary lock 我们可以定位到其 primarykey 是 Bob。Bob 的当前状态代表了整个事务 t0 当前的状态
上面所述都是比较乐观的场景,但是现实上可能会遇到各种并发问题或者网络问题,导致 Prewrite 的前置检查失败。
假如只有一个事务 t
事务 t 刚刚执行了 Prewrite 、或者Prewrite超时 后,可能由于网络原因又对同一个事务 t 调用 Prewrite,会返回 OK (1.1)
事务 t 已经 Commit Primary Key、Commit Secondary Key 完毕了,由于网络原因又对同一个事务 t 调用 Prewrite,会返回 OK (2.1)
事务 t 已经 Rollback 完毕了,由于网络原因又对同一个事务 t 调用 Prewrite,会返回 WriteConflict(2.2)
假如有事务 t、t1 ,他们更新的 KEY 相同,假如事务 t 完毕了,事务 t1 才启动,
事务 t1 执行了 Prewrite /Commit Primary Key/Commit Secondary Key/Rollback后,由于网络原因又对已经完毕的事务 t 调用 Prewrite,
假如事务 t 已经 Commit,会返回 OK (1.2)(2.1)
假如事务 t 已经 Rollback,会返回 WriteConflict (1.3)(2.2)
假如有事务 t、t1 ,他们更新的 KEY相同,事务t 先启动后,事务 t1 后启动 (t.start_ts < t1.start_ts)
事务 t1 已经执行了 Prewrite,未来得及 Commit,这时候 t 才进行 Prewrite,会返回 KeyIsLocked (1.4)
事务 t1 已经执行了 Prewrite 后 Down 了,这时候 t 才进行 Prewrite,会返回 KeyIsLocked (1.4)
事务 t1 已经执行了 Commit Primary Key、Commit Secondary Key,这时候 t 才进行 Prewrite,会返回 WriteConflict (2.3)
事务 t1 已经执行了 Rollback,这时候 t 才进行 Prewrite,会返回 WriteConflict(2.3)
下面将会讲解各个异常检查的细节逻辑以及其相应的样例场景。
Prewrite 的 LOCK 前置检查失败的情况下,例如下图中 Bob 这个 KEY 就存在着一个 primary lock:
并不是直接报错,而是会进行进一步的检查。
如果发现其中一个 Key 已经被加锁,判断这个 lock 是不是本事务的 (lock.ts=t.start_ts)
1.1 如果是的话,那么就是接口重复调用,保持幂等,返回 OK (场景一)
否则的话,说明这个 lock 不是本事务的,需要根据 t.start_ts 继续搜索 write_cf 中的 write记录
1.2搜索到 Commit记录的话,说明本事务已经提交,那么就是接口重复调用,保持幂等,返回OK
Commit 记录是指:
( record.start_ts = t.start_ts && record.type != Rollback) 的 write记录 (场景二)
1.3 搜索到 Rollback 记录的话,说明本事务已经回滚,会返回 WriteConflict
Rollback 记录指的是:
符合条件 ( record.start_ts = t.start_ts && record.type = Rollback) 的 write 记录 (场景三)
或者,符合条件 (record.commit_ts = t.start_ts && has_overlapped_rollback = true )的 write记录 (场景四)
1.4 None 记录,也就是没有找到本事务的记录,会返回 KeyIsLocked 错误,附带 lock 信息,等待后续 CheckTxnStatus 查看 lock 对应的事务状态
None 记录指的是:
符合条件 ( record.start_ts != t.start_ts && record.commit_ts != t.start_ts) 的 write记录 (场景五、场景六、场景七)
或者,符合条件 (record.commit_ts = t.start_ts && has_overlapped_rollback = false ) 的 write记录 (场景八)
以上述 Bob and Joe’s 事务 t0 为例,t0 已经 Prewrite ,此时状态结果是:
这个时候,如果因为网络原因,client 没有收到 tikv 返回的 Prewrite Resp,因此 tidb 重试重新发送了 Prewrite 请求:
发现其中一个 Key Bob 已经被加锁,
发现这个 lock 是本事务的 (lock.ts=t.start_ts)
接口重复调用,保持幂等,返回 OK
以上述 Bob and Joe’s 事务 t0 为例,t0 已经 Commit the secondary,其 start_ts=7,commit_ts=8 结果是:
又有 t1事务,目标是扣除Joe 的账户 7 元,事务 t1 的 start_ts 是 9,commit_ts=10
又有 t2 事务,目标是给 Joe 的账户转账 6 元,事务 t2 的 <
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。