TIKV 源码学习笔记 -- 分布式事务接口 Commit/Rollback
作者: ylldty 原文来源:https://tidb.net/blog/2123ce5d
前言
上一篇介绍了 Prewrite 接口,这篇我们继续介绍 Commit/Rollback 接口,Cleanup 接口实际上和 Rollback 接口类似。
除此之外,还有 CheckTxnStatus/ ResolveLock / CheckSecondaryLocks 关键接口,由于篇幅有限,只能后面有机会再聊
Commit
参数
KEYS:Commit提交的涉及的 KEYS,相关的KEYS和Prewrite相同LOCK_TS:Commit需要消除的LOCK TS,一般也是事务的start_tsCOMMIT_TS: 提交的最终commit_ts
以 UPDATE 语句为例:
其他语句类似,区别不大,这里不在赘述。
代码简读
对每个
KEY都调用commit函数进行提交操作使用
load_lock函数来检查是否含有KEY对应的LOCK,我们预期应该存在Prewrite留下的LOCK如果没有发现
LOCK或者不是本事务的LOCK,:调用
get_txn_commit_record观察是否已经提交完毕,如果已经提交,那么可以提前返回OK如果发现了回滚记录,或者没有找到任何记录,那么返回
ERR: TxnLockNotFound如果发现了本事务的
LOCK,首先检查一下lock.min_commit_ts必须大于commit_ts如果
LOCK类型是正常的锁,那么删除锁,并且添加新的write记录即可正常返回如果
LOCK类型是悲观锁,这个是非预期的,这时候commit操作实际上就是删除悲观锁即可 (并不需要write CF上的回滚记录)。可能是因为
pessimistic rollback请求未能发送到TIKV,也可能是TIKV由于某种情况下突然收到了pessimistic lock请求,个人理解这些特殊场景可能并不是二阶段过程中会发生的,因为Prewrite成功后不可能将常规的锁转为悲观锁,根据注释大概率应该是resolve lock过程中可能遇到的场景还有一种比较特殊的情况,那就是存在并发的两个事务,
t1与t2,t1开启的时间很早,也就是t1.start_ts < t2.start_tst1对KEY调用了Prewrite进行了加锁t2开启的时间比较晚,也想对KEY进行加锁,发现有并发事务的锁冲突,因此采取了回滚。回滚的时候,会留下
write记录,该write记录的write.commit_ts = t2.start_ts那么其实会有一个隐患,如果
t1提交的时候,t1.commit_ts恰好和t2.start_ts相同的话,那么t1提交write记录就会覆盖t2的回滚记录正常来说,如果在提交
t1事务的时候,先来看一眼write现有记录的话,可以简单的避免这个问题。但是每次提交都查询write记录的话,代价稍微有点高。但是我们每次进行
commit的时候,都避免不了去加载LOCK信息因此,引入了
lock.rollback_ts,每当其他事务发生锁冲突因此需要回滚的时候,我们都会更新这个rollback_ts数组。如果t1发现自己的commit_ts命中了lock.rollback_ts,那么写write记录的时候需要小心一些,设置overlapped_rollback为true,标志这个write记录其实是叠加了两个事务的commit和rollback
Rollback
场景
和直观认知可能不太一样,TIKV 的 Rollback 接口一般情况下并不是 sql 的 rollback 语句触发的。
对于乐观事务来说,由于事务过程中,没有加任何锁,因此 sql rollback 语句实际上并不需要调用 tikv 的接口处理,只需要将 Buff 的 put 数据清空即可。
对于悲观事务来说,事务过程中加了悲观锁,但是 sql rollback 语句实际触发的是 pessimistic_rollback 这个接口,专门用于清理悲观锁。
TIKV 的 Rollback 接口常见于乐观事务写冲突的时候,乐观事务在进行二阶段提交过程中,prewrite 过程中发现了写冲突,这时候就需要调用 TIKV 的 Rollback。
t1: begin optimistic;t1: DELETE FROM MANAGERS_UNIQUE where FIRST_NAME='Brad7';t2: begin optimistic;t2: DELETE FROM MANAGERS_UNIQUE where FIRST_NAME='Brad7';t2: commit;t1: commit; ERROR 9007 (HY000): Write conflict;
实际上对于写冲突的 Rollback, 之前 prewrite 也大概率并没有加锁,因此 Rollback 不需要清理锁,也不需要清楚 default CF 的数据,只需要添加一个 Rollback write 记录。
参数
KEYS:Commit提交的涉及的KEYS,相关的KEYS和Prewrite相同LOCK_TS:Commit需要消除的LOCK TS,一般也是事务的start_ts
Overlapped Rollback 回滚记录
我们知道回滚记录是个比较特殊的 write 记录,不仅仅是 write.type 是 rollback 类型的,而且还因为其 write 记录的 commitTS 与事务的 startTS 是相同的,TIKV 这样设计应该是为了减少和 PD 的交互,少获取一次 TS,节省系统消耗。
因此普通的提交记录是这样的 KEY-VALUE 格式:
{KEY_CommitTS: { write.type=put,write.startTS=startTS } }
而回滚记录一般是这样的 KEY-VALUE 格式:
{KEY_StartTS: { write.type=rollback,write.startTS=startTS } }
那么这样就会有一个问题,那就是很多事务的 commitTS 也不是 PD 获取的,而是通过计算得到的,例如 Async Commit。那么就可能会遇到这个场景:
T1事务启动startTS=start_t1, 采用了Async Commit的方式,计算出commitTS=commit_t1,提交记录的KEY是KEY_commit_t1T2事务启动startTS=start_t2,然后被回滚,因此其回滚记录的KEY是KEY_start_t2由于
commit_t1并不是 PD 获取的,而start_t2是 PD 获取的 ts,因此就有概率commit_t1==start_t2,也就是说两个事务的提交记录和回滚记录在write CF上重叠了这个时候,就需要一个属性值
Overlapped,当一个提交记录的Overlapped为true的时候,就代表这其实是两个记录,一个提交记录一个回滚记录
保护模式和非保护模式回滚记录
当我们事务冲突很严重的时候,就容易有多条的回滚记录,这对于 TIKV 的 mvcc 扫描来说效率太慢了。因此 TIKV 有个优化,在 write CF 上面,对于一个 KEY,只保留最新的那个回滚记录即可,其他回滚记录可以直接删除。
但是为了正确性考虑,必须防止已经对 KEY 进行了回滚操作,后面突然由于网络原因又出现对 KEY 调用了 prewrite 和 commit,导致回滚的事件被错误的提交。
因此只能对部分 KEY 进行这种 collapse 删除优化。
具体的就是对于 rowID、唯一索引来说,采用保护模式的回滚,该回滚记录不会被删除。这样每次事务被错误的 commit 的时候,都可以通过被保护的回滚记录了解到这个事务实际上已经被提交了。
对于普通索引,采用非保护模式,可能被其他事务更新的 rollback 记录删除,也可能遇到需要 Overlapped 的场景,并不设置 Overlapped 为 true。
最后实际上最后结果就是:普通索引上面即使被回滚了,但是却找不到任何回滚的记录。
代码简读
对每个
KEY都调用cleanup函数进行回滚操作。(而且是以非保护模式下来调用)使用
load_lock函数来检查是否含有KEY对应的LOCK,我们预期应该存在事务留下的LOCK如果发现了本事务的锁,
lock.ts == txn.start_ts,执行rollback_lock进行回滚操作rollback_lock为了保险起见,会再次通过get_txn_commit_record函数查看write的最新记录如果发现
write上有当前事务的提交记录,直接panic如果发现有
OverlappedRollback的记录或者回滚记录 (SingleRecord::Rollback),说明之前已经添加了write回滚记录,删除了default上面的value数据,那么现在只需要把LOCK记录删除即可如果没有发现任何提交记录或者回滚记录,那么
如果
LOCK是PUT类型、且已经写入default CF Value,那么需要删除default CF Value非保护模式下利用
make_rollback生成rollback类型的write记录特别需要注意的是,由于是非保护模式下,所以如果恰好
rollback记录 {key_startTS} 与其他事务的提交记录 {key_commitTS} 重叠 (可能t1的startTS恰好是t2事务的commitTS),那么一般情况下可以省略rollback记录的写入,为集群减少负担。但是如果
KEY是悲观事务的Primary KEY的话,就需要将提交记录{key_commitTS} 设置一个overlapped_rollback标记删除
LOCK记录如果没有发现锁或者发现的锁并不是本事务的,而是其他事务的
LOCK,那么需要调用check_txn_status_missing_lock如果发现有本事务的
OverlappedRollback的记录或者回滚记录 (SingleRecord::Rollback),说明已经回滚完成,直接返回OK即可终止回滚流程如果发现有本事务提交记录的话,返回
ErrorInner::Committed如果没有找到任何本事务
write记录的话如果发现了其他事务的锁:首先需要调用
mark_rollback_on_mismatching_lock在这个LOCK上面添加回滚LockTS标记,这样这个lock所涉及的事务在提交后,如果发现自己的commitTS和LockTS重叠的话,需要设置一下overlap标记保护模式下调用
make_rollback写入rollback记录,确保这个回滚记录不会被删除删除
collapse以前的非保护rollback记录
rollback_lock
为了保险起见,会再次通过
get_txn_commit_record函数查看write的最新记录如果发现
write上有当前事务的提交记录,直接panic如果发现有
OverlappedRollback的记录或者回滚记录 (SingleRecord::Rollback),说明之前已经添加了write回滚记录,删除了default上面的value数据,那么现在只需要把LOCK记录删除即可如果没有发现任何提交记录或者回滚记录,那么
如果
LOCK是PUT类型、且已经写入default CF Value,那么需要删除default CF Value非保护模式下利用
make_rollback生成rollback类型的write记录特别需要注意的是,由于是非保护模式下,所以如果恰好
rollback记录 {key_startTS} 与其他事务的提交记录 {key_commitTS} 重叠 (可能t1的startTS恰好是t2事务的commitTS),那么一般情况下可以省略rollback记录的写入,为集群减少负担。但是如果
KEY是悲观事务的Primary KEY的话,就需要将提交记录{key_commitTS} 设置一个overlapped_rollback标记删除
collapse以前的非保护rollback记录删除
LOCK记录
check_txn_status_missing_lock
如果发现有本事务的
OverlappedRollback的记录或者回滚记录 (SingleRecord::Rollback),说明已经回滚完成,直接返回OK即可终止回滚流程如果发现有本事务提交记录的话,返回
ErrorInner::Committed如果没有找到任何本事务
write记录的话 (这个场景可能比较少见)首先需要调用
mark_rollback_on_mismatching_lock在这个LOCK上面添加回滚LockTS标记,这样这个lock所涉及的事务在提交后,如果发现自己的commitTS和LockTS重叠的话,需要设置一下overlap标记保护模式下调用
make_rollback写入rollback记录,确保这个回滚记录不会被删除删除
collapse以前的非保护rollback记录
Cleanup
Cleanup 和 Rollback 实际上调用的代码区别不大,关键点就是调用 action::cleanup 函数的时候,传递的 protect_rollback 参数是 true,也就是说 Cleanup 接口的回滚记录全部都是保护模式的。
Cleanup 比较重要的作用就是清理当前事务中,已经不需要的锁信息。因此,为了保险起见 ,Cleanup 接口会留下保护类型的回滚记录,防止网络异常原因导致的 stale prewrite 请求,并且请求成功导致事务被错误提交。
关于何时调用 Cleanup 何时调用 Rollback ,需要具体看 tikv-client 的逻辑甚至看 TIDB 的逻辑,目前笔者对此了解不多。只能从 TIKV 的代码来猜测,Rollback 应该是用于非常确定的场景,即使出现了当前事务的 stale prewrite 请求,也不会导致事务会被成功提交,因此其回滚记录可以是非保护模式的,即使被删除了也无所谓。其他场景都是需要 Cleanup 接口,把回滚记录保护起来,拦截阻止 stale prewrite 请求的成功。
版权声明: 本文为 InfoQ 作者【TiDB 社区干货传送门】的原创文章。
原文链接:【http://xie.infoq.cn/article/da29d66b914b519bffb7e2346】。文章转载请联系作者。







评论