TiDB 的事务的实现采用了 MVCC(多版本并发控制)机制,当新写入的数据覆盖旧的数据时,旧的数据不会被替换掉,而是与新写入的数据同时保留,并以时间戳来区分版本。Garbage Collection (GC) 的任务便是清理不再需要的旧数据。
一个 TiDB 集群中会有一个 TiDB 实例被选举为 GC leader,GC 的运行由 GC leader 来控制。
GC 会被定期触发。每次 GC 时,首先,TiDB 会计算一个称为 safe point 的时间戳,接下来 TiDB 会在保证 safe point 之后的快照全部拥有正确数据的前提下,删除更早的过期数据。每一轮 GC 分为以下三个步骤:
- "Resolve Locks" 阶段会对所有 Region 扫描 safe point 之前的锁,并清理这些锁。
- "Delete Ranges" 阶段快速地删除由于 DROP TABLE / DROP INDEX 等操作产生的整区间的废弃数据。
- "Do GC" 阶段每个 TiKV 节点将会各自扫描该节点上的数据,并对每一个 key 删除其不再需要的旧版本。
默认配置下,GC 每 10 分钟触发一次,每次 GC 会保留最近 10 分钟内的数据(即默认 GC life time 为 10 分钟,safe point 的计算方式为当前时间减去 GC life time)。如果一轮 GC 运行时间太久,那么在一轮 GC 完成之前,即使到了下一次触发 GC 的时间也不会开始下一轮 GC。另外,为了使持续时间较长的事务能在超过 GC life time 之后仍然可以正常运行,safe point 不会超过正在执行中的事务的开始时间 (start_ts)。
TiDB 事务基于 Google Percolator 模型,通过两阶段提交完成事务提交。第一阶段给事务所有相关 Key 加锁,选定一把主锁(Primary),其余副锁(Secondary)记录主锁的引用;第二阶段为主锁所在 Key 生成 Write 记录并释放主锁。Write 记录记录 Key 的写入、删除或回滚历史,主锁对应的 Write 记录类型标识事务提交结果,随后再逐个处理副锁。 故障可能导致副锁残留,正常情况下可通过副锁里的主锁引用判断事务状态。一旦主锁信息被 GC 清除、副锁还未处理,事务状态就无法判定,数据正确性无法保障。
Resolve Locks 负责清理 safe point 之前的锁:主锁已提交则提交对应锁,主锁已回滚则回滚对应锁;主锁长期持锁无提交 / 回滚动作时,将事务判定为超时并回滚。
说明
在云数据库 TiDB 版 Essential 集群中 Resolve Locks 默认为 LEGACY 模式:由 GC leader 对所有的 Region 发送请求扫描过期的锁,并对扫到的锁查询 Primary 的状态,再发送请求对其进行提交或回滚。
在执行 DROP TABLE / DROP INDEX 等操作时,会有大量连续的数据被删除。如果对每个 key 都进行删除操作、再对每个 key 进行 GC 的话,那么执行效率和空间回收速度都可能非常的低下。事实上,这种时候 TiDB 并不会对每个 key 进行删除操作,而是将这些待删除的区间及删除操作的时间戳记录下来。Delete Ranges 会将这些时间戳在 safe point 之前的区间进行快速的物理删除。
这一步即删除所有 key 的过期版本。为了保证 safe point 之后的任何时间戳都具有一致的快照,这一步删除 safe point 之前提交的数据,但是会对每个 key 保留 safe point 前的最后一次写入(除非最后一次写入是删除)。
在进行这一步时,TiDB 只需要将 safe point 发送给 PD,即可结束整轮 GC。TiKV 会自行检测到 safe point 发生了更新,会对当前节点上所有作为 Region leader 进行 GC。与此同时,GC leader 可以继续触发下一轮 GC。