文档章节

TIKV 分布式事务--Prewrite 接口乐观事务详解

TiDB社区干货传送门
 TiDB社区干货传送门
发布于 02/09 00:00
字数 3864
阅读 17
收藏 0

作者: ylldty 原文来源:https://tidb.net/blog/c652507b

前言

上一篇文章大致讲了 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 前,需要对 Prewrite 涉及的 KEY 都要进行如下检查:

  • 检查在 lock_cf 中没有记录,也就是没有锁

  • 检查在 write_cf 中没有大于等于当前事务 start_ts 的记录

前置检查通过样例

例如下面的例子 (start_ts=7) 就可以通过前置检查:

可以看到存在两个 KEY,一个是 Bob,一个是 Joe。两个 KEY 的 lock_cf 都是空的,同时 write_cf 的最新记录是 6,小于 start_ts(7)

LOCK 检查失败样例

例如下面的例子 (start_ts=7) ,前置检查就会失败,因为 Boblock_cf 存在一个 ts 为 9 的 primary lock

WRITE 检查失败样例

例如下面的例子 (start_ts=7) ,前置检查就会失败,因为 Bobwrite_cf 存在一个 commit_ts=9 的记录:

Prewrite —— 操作

主要流程

前置检查通过后,我们开始进行真正的 Prewrite 操作。

作为2PC 的第一阶段,预提交。目的是将事务涉及的多个 KEY-VALUE 写入 default_cf,同时将在 lock_cf 上加锁

  • KEY-VALUE 写入 default_cf

  • lock 信息写入 lock_cf 上加锁

样例

Prewrite 操作前,存储的事务状态为:

BobJoe 进行 Prewrite 操作后,存储的事务状态为:

值得注意的是,tidb 指定 Bobprimary keyBob 写入的 lockprimary lock。指定 Joesecondary keyJoe 写入的 locksecondary lock

通过 Joesecondary lock 我们可以定位到其 primary keyBobBob 的当前状态代表了整个事务 t0 当前的状态

Prewrite —— 异常检查概览

上面所述都是比较乐观的场景,但是现实上可能会遇到各种并发问题或者网络问题,导致 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,会返回 WriteConflict2.2

假如有事务 tt1 ,他们更新的 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

假如有事务 tt1 ,他们更新的 KEY 相同,事务 t 先启动后,事务 t1 后启动 (t.start_ts < t1.start_ts)

  • 事务 t1 已经执行了 Prewrite,未来得及 Commit,这时候 t 才进行 Prewrite,会返回 ***KeyIsLocked ***(1.4

  • 事务 t1 已经执行了 PrewriteDown 了,这时候 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 异常检查

PrewriteLOCK 前置检查失败的情况下,例如下图中 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 记录 (场景八)

场景样例

场景一:Prewritten

以上述 Bob and Joe’s 事务 t0 为例,t0 已经 Prewrite ,此时状态结果是:

这个时候,如果因为网络原因,client 没有收到 tikv 返回的 Prewrite Resp,因此 tidb 重试重新发送了 Prewrite 请求:

  • 发现其中一个 Key Bob 已经被加锁,

    • 发现这个 lock 是本事务的 (lock.ts=t.start_ts)
  • 接口重复调用,保持幂等,返回 OK

场景二:Committed

以上述 Bob and Joe’s 事务 t0 为例,t0 已经 Commit the secondary,其 start_ts=7,commit_ts=8 结果是:

又有 t1 事务,目标是扣除 Joe 的账户 7 元,事务 t1start_ts 是 9,commit_ts=10

又有 t2 事务,目标是给 Joe 的账户转账 6 元,事务 t2start_ts 是 10,TIKV 刚刚处理完 Prewrite 请求,此时事务的存储状态为:

这个时候,如果因为网络原因,tikv 又收到了对 t0 (start_ts=7) 的 Prewrite 请求:

  • 检查 Key Joe 存在锁,而且这个 lock 不是本事务的锁 ( lock.ts(9) != start_ts(7) )

  • 继续搜索 write_cf 数据

  • 检查到 Joecommit_ts >= 7 的记录

    • 搜索到一个记录 record.commit_ts=10record.start_ts=9

    • 不符合条件,跳过

    • 搜索到一个记录 record.commit_ts=8record.start_ts=7

    • 符合 Commit 记录的条件:record.start_ts=t.start_ts=7

  • 接口重复调用,保持幂等,返回 OK

场景三:Rollbacked

以上述 Bob and Joe’s 事务 t0 为例,事务 t0start_ts 是 7,t0 由于某些原因已经 rollback ,其结果是:

又有 t1 事务,目标是扣除 Joe 的账户转账 6 元,事务 t1start_ts 是 9,commit_ts=10

又有 t2 事务,目标是给扣除 Joe 的账户转账 2 元,事务 t2start_ts 是 11,TIKV 刚刚处理完 Prewrite 请求,此时事务的存储状态为:

这个时候,如果因为网络原因,tikv 又收到了对 t0 (start_ts=7) 的 Prewrite 请求:

  • 检查 Key Joe 存在锁,而且这个 lock 不是本事务的锁 ( lock.ts(11) != t.start_ts(7) )

  • 继续搜索 write_cf 数据

  • 检查到 Joecommit_ts >= 7 的记录

    • 搜索到一个记录 record.commit_ts=10record.start_ts=9

      • 不符合条件,跳过
    • 搜索到一个记录 record.commit_ts=7record.start_ts=7, record.type=rollback

      • 符合 Rollback 记录的条件:record.start_ts = t.start_ts && record.type = Rollback
  • 返回 WriteConflict

场景四:Rollbacked

以上述 Bob and Joe’s 事务 t0 为例,t0 由于某些原因已经 rollback ,其 start_ts=8,其结果是:

又有 t1 事务,目标是为 Joe 的账户转账 6 元,事务 t1start_ts 是 7,commit_ts是 8,已经提交完毕

值得注意的是,此时 t0.start_ts = t1.commit_ts

我们发现 t1 事务的 Joecommit write 记录和 t0 事务的 rollback 记录重叠了,因此 TIKV 会对 t1commit 记录添加一个标志: has_overlapped_rollback=true

又有 t2 事务,目标是给扣除 Joe 的账户 2 元,事务 t2start_ts 是 9,commit_ts 是 10

又有 t3 事务,目标是给扣除 Joe 的账户 2 元,TIKV 刚刚处理完 t3Prewrite 请求,事务 t3start_ts 是 11, 此时事务的存储状态为:

这个时候,如果因为网络原因,tikv 又收到了对 t0 (start_ts=8) 的 Prewrite 请求:

  • 检查 Key Joe 存在锁,而且这个 lock 不是本事务的锁 ( lock.ts(11) != t.start_ts(8) )

  • 继续搜索 write_cf 数据

  • 检查到 Joecommit_ts >= 8 的记录

    • 搜索到一个记录 record.commit_ts=10record.start_ts=9

      • 不符合条件,跳过
    • 搜索到一个记录 record.commit_ts=8record.start_ts=7, has_overlapped_rollback = true

      • 符合 Rollback 记录的条件: record.commit_ts = t.start_ts && has_overlapped_rollback = true
  • 返回 WriteConflict

场景五:None

以上述 Bob and Joe’s 事务 t0 为例,start_ts 为 8, t0 刚刚进行 Prewrite 成功, 状态结果是:

假如此时有个和 t0 并行的事务 t1,start_ts 为 7, 目标是扣除 Joe 的账户 4 元,。

  • 此时对 t1 进行 Prewrite 后,扫描到 Joe t0 事务的 secondary lock 记录

  • 继续搜索 write_cf 数据

  • 检查到 Joecommit_ts >= 7 的记录

    • 搜索到一个记录 record.commit_ts=6,record.start_ts=5

    • 不符合条件,结束搜索,write_ts 并没有 Joe ts 为 8 的记录

  • 返回 KeyIsLocked 错误,等待后续调用 CheckTxnStatus 检查 t0 事务状态

场景六:None

以上述 Bob and Joe’s 事务 t0 为例,start_ts 为 8,commit_ts 为 9, t0 已经 Commit the primary 成功, 状态结果是:

假如此时有个和 t0 并行的事务 t1,start_ts 为 7, 目标是扣除 Joe 的账户 4 元。

  • 此时对 t1 进行 Prewrite 后,扫描到 Joe t0 事务的 secondary lock 记录

  • 继续搜索 write_cf 数据

  • 检查到 Joecommit_ts >= 7 的记录

    • 搜索到一个记录 record.commit_ts=6,record.start_ts=5

    • 不符合条件,结束搜索,write_ts 并没有 Joe ts 为 8 的记录

<!---->

  • 返回 KeyIsLocked 错误,等待后续调用 CheckTxnStatus 检查 t0 事务状态

场景七:None

以上述 Bob and Joe’s 事务 t0 为例,start_ts 为 8,commit_ts 为 9, 已经提交完毕。

又有 t2 事务,目标是扣除 Joe 的账户 2 元,事务 t2start_ts 是 10,commit_ts 是 11,已经提交完毕

又有 t3 事务,目标是扣除 Joe 的账户 2 元,TIKV 刚刚处理完 t3Prewrite 请求,事务 t3start_ts 是 12, 此时事务的存储状态为:

假如此时有个并行的事务 t1start_ts 为 7, 目标是扣除 Joe 的账户 4 元。

  • 检查 Key Joe 存在锁,而且这个 lock 不是本事务的锁 ( lock.ts(12) != t.start_ts(7) )

  • 继续搜索 write_cf 数据

  • 检查到 Joecommit_ts >= 7 的记录

    • 搜索到一个记录record.commit_ts=11record.start_ts=10

      • 不符合条件,跳过
    • 搜索到一个记录 record.commit_ts=9record.start_ts=8

      • 不符合条件,跳过
    • 搜索到一个记录 record.commit_ts=6record.start_ts=5

      • 已经不符合 commit_ts >= 7

      • 搜索结束

  • 返回 KeyIsLocked

场景八:None

以上述 Bob and Joe’s 事务 t0 为例,事务 t0start_ts 是 7,commit_ts 是 8,已经提交

又有 t2 事务,目标是扣除 Joe 的账户 2 元,事务 t2start_ts 是 9,commit_ts 是 10,已经提交完毕

又有 t3 事务,目标是扣除 Joe 的账户 2 元,TIKV 刚刚处理完 t3Prewrite 请求,事务 t3start_ts 是 11, 此时事务的存储状态为:

这个时候,出现了 t1 事务,目标是 Joe 的账户转账 6 元,事务 t1start_ts 是 8

值得注意的是,此时 t0.commit_ts = t1.start_ts= 8

tikv 又收到了对 t1 (start_ts=8) 的 Prewrite 请求:

  • 检查 Key Joe 存在锁,而且这个 lock 不是本事务的锁 ( lock.ts(11) != t.start_ts(8) )

  • 继续搜索 write_cf 数据

  • 检查到 Joecommit_ts >= 8 的记录

    • 搜索到一个记录 record.commit_ts=10record.start_ts=9

      • 不符合条件,跳过
    • 搜索到一个记录 record.commit_ts=8record.start_ts=7, has_overlapped_rollback = false

      • 符合 None 记录的条件: record.commit_ts = t.start_ts && has_overlapped_rollback = false
  • 返回 KeyIsLocked

Prewrite —— WRITE 异常检查

PrewriteWRITE 前置检查失败的情况下,并不是直接报错,而是会进行进一步的检查。

主要流程

如果发现其中一个 Keywrite_cf 已经有新的记录 (record.commit_ts >= t.start_ts)

  • 继续搜索 write_cf 中是否含有本事务的记录

    • 2.1)如果是 Commit 记录的话,说明本事务已经提交,那么就是接口重复调用,保持幂等,返回 OK

      • Commit 记录是指:

        • ( record.start_ts = t.start_ts && record.type != Rollback) 的 write 记录 (场景一场景二)
    • 2.2)如果是 Rollback 记录的话,说明本事务已经回滚,会返回 WriteConflict

      • Rollback 记录指的是:

        • 符合条件 ( record.start_ts = t.start_ts && record.type = Rollback) 的 write 记录 (场景三)

        • 或者,符合条件 (record.commit_ts = t.start_ts && has_overlapped_rollback = true )write 记录

    • 2.3)没有找到本事务的记录,说明有其他事务并行更新,会返回 WriteConflict,可能需要业务重试事务

      • None 记录指的是:

        • 符合条件 (record.commit_ts = t.start_ts && has_overlapped_rollback = false )write 记录

        • 或者,符合条件 ( record.start_ts != t.start_ts && record.commit_ts != t.start_ts) 的 write 记录 (场景四)

场景样例

由于 Write 的异常场景检查和 Lock 的异常场景检查类似,下面只列举了几个比较典型的 Write 的异常检查场景,其他场景可以参考 Lock 的异常。

场景一:Committed

以上述 Bob and Joe’s 事务 t0 为例,t0 已经 Commit the secondary,其 start_ts=7,commit_ts=8 结果是:

这个时候,如果因为网络原因,tikv 又收到了对 t0 (start_ts=7) 的 Prewrite 请求:

  • 检查 Key Joe 没有锁

  • 继续搜索 write_cf 数据

  • 检查到 Joecommit_ts >= 7 的记录

    • 搜索到一个记录 record.commit_ts=8record.start_ts=7

      • 符合 Commit 记录的条件:record.start_ts=t.start_ts=7
  • 接口重复调用,保持幂等,返回 OK

场景二:Committed

以上述 Bob and Joe’s 事务 t0 为例,t0 已经 Commit the secondary,其 start_ts=7,commit_ts=8

又有 t1 事务,目标是扣除 Joe 的账户 7 元,事务 t1start_ts 是 9,commit_ts=10,已经提交完毕。

此时事务的存储状态为:

这个时候,如果因为网络原因,tikv 又收到了对 t0 (start_ts=7) 的 Prewrite 请求:

  • 检查 Key Joe 没有锁

  • 继续搜索 write_cf 数据

  • 检查到 Joecommit_ts >= 7 的记录

    • 搜索到一个记录 record.commit_ts=10record.start_ts=9

      • 不符合搜索条件,跳过
    • 搜索到一个记录 record.commit_ts=8record.start_ts=7

      • 符合 Commit 记录的条件:record.start_ts=t.start_ts=7
  • 接口重复调用,保持幂等,返回 OK

场景三:Rollbacked

以上述 Bob and Joe’s 事务 t0 为例,事务 t0start_ts 是 7,t0 由于某些原因已经 rollback ,其结果是:

又有 t1 事务,目标是扣除 Joe 的账户转账 6 元,事务 t1start_ts 是 9,commit_ts=10

  • 检查 Key Joe 没有锁

  • 继续搜索 write_cf 数据

  • 检查到 Joecommit_ts >= 7 的记录

    • 搜索到一个记录 record.commit_ts=10record.start_ts=9

      • 不符合搜索条件,跳过
    • 搜索到一个记录 record.commit_ts=7record.start_ts=7, record.type=rollback

      • 符合 Rollback 记录的条件:record.start_ts = t.start_ts && record.type = Rollback
  • 返回 WriteConflict

场景四:None

以上述 Bob and Joe’s 事务 t0 为例,t0start_ts=7commit_ts=9t0 已经 Commit the secondary成功, 状态结果是:

假如此时有个和 t0 并行的事务 t1,事务 t1start_ts 是 8,目标是扣除 Joe 的账户 4 元

  • 此时对 t1 进行 Prewrite 后,没有扫描到 Joe t0 事务的 lock 记录

  • 继续搜索 write_cf 数据

  • 检查到 Joecommit_ts >= 8 的记录

    • 扫描到了Joe record.commit_ts=9record.start_ts=7

      • 不符合搜索条件,跳过
    • 扫描到了Joe record.commit_ts=6record.start_ts=5

      • commit_ts<7

      • 搜索结束

  • 返回 WriteConflict

TiDB社区干货传送门
粉丝 32
博文 1316
码字总数 1674997
作品 0
广州
私信 提问
加载中
点击引领话题?
双目视觉理论篇

相机模型与四种参考坐标系 上图中右下角的黑点是真实世界的一个点,最左边的灰色部分是一张数字照片,称为像平面,单位为毫米(mm)。青色的格子则是像平面中一个一个的像素。我们现在需要知道...

算法之名
昨天
51
0
TiDB v7.1.1三地五中心,TiDB POC最佳实践探索

原文来源:https://tidb.net/blog/b4732d88 POC测试背景 在某地震多发省,为了避免地震造成的机房级灾难,或者城市级灾难,导致整个系统不可用,拟建设一套三地五中心五副本分布式高可用数据...

TiDB社区干货传送门
2023/09/28
2
0
首页推荐:彩票导师一对一带上岸带赚钱计划

彩票导师一对一带上岸带赚钱计划?【網子:fc 665? cc】?【 Q?:85,62-329】一对一单带?稳定计划群?最重要还是要找对一个能引领你的人我是这么认为的:心态只能让我们锦上添花,但是并...

osc_4962273979
前天
0
0
Redis 被分叉了。

嘿朋友们。我需要更好地更新这个通讯,而不是在现在每个只有三个用户的五百万个社交平台上随意发布链接。自从我上次发电邮以来,我写了如下内容: 一篇关于GGUF的长篇文章 Check out this g...

vickibo53314cfa41
04/18
11
0
在Go中执行比特币交易的方法

比特币近年来风靡一时。同样,谷歌开发的编程语言Golang也备受关注。今天我将指导您如何使用Golang进行简单的比特币交易。希望在本教程之后,您能够不使用任何DApps进行比特币转账。教程要求...

coderoa69a90f74b5
前天
24
0

没有更多内容

加载失败,请刷新页面

加载更多

{{formatHtml(o.title)}}

{{i}}-{{formatHtml(o.content)}}

{{o.author.name}}
{{o.pubDate | formatDate}}
{{o.viewCount | bigNumberTransform}}
{{o.replyCount | bigNumberTransform}}

暂无文章

便宜云服务器
登录后可查看更多优质内容
返回顶部
顶部
http://www.vxiaotou.com