导读
本文介绍了 TiDB 中 MVCC(多版本并发控制)机制的原理和相关排查手段。TiDB 使用 MVCC 机制实现事务,在写入新数据时不会直接替换旧数据,而是保留旧数据的同时以时间戳区分版本。当历史版本堆积过多时,会导致读写性能下降。为了解决这个问题,TiDB 使用 Garbage Collection(GC)定期清理不再需要的旧数据。文章从 TiDB 中 MVCC 版本的生成原理、数据写入过程和 TiDB 版本堆积常见排查手段等方面进行了详细介绍。
TiDB 的事务的实现采用了 MVCC(多版本并发控制)机制,当新写入的数据覆盖旧的数据时,旧的数据不会被替换掉,而是与新写入的数据同时保留,并以时间戳来区分版本。Garbage Collection(GC)的任务便是清理不再需要的旧数据。


在 TIDB 层,我们最初收到的是一个关系型表的数据,TiDB 会将这个关系型表数据转化成 key-value,同时调用分布式事务接口,将 key-value 数据写入到 TiKV。 在 TIKV 层,我们采用 MVCC 机制提供了分布式事务接口,最终所有的写入都会转化成一条 MVCC key-value 格式写入到 raftstore. 说到 MVCC 格式的 key-value, 无非就是每一个 key 上都有一个版本号,代表其提交的先后顺序。后面我们将这类格式的数据统一称为 MVCC key-value 对。 在 raftstore 层,则最终将数据以 key-value 的形式,写入到 rocksdb 中。(注意,rocksdb(https://docs.pingcap.com/tidb/stable/rocksdb-overview )本身基于 LSM 架构实现,所以它也有 MVCC 的概念,本文不做详细介绍,只对 TiDB 相关的内容点到为止)
insert into students set name="Bob",age=12,score=99
1.1 TiDB 侧 SQL table 转为 Key-Value

主键对需要保证 key 唯一性:主键值 => 本行所有列数据 唯一索引按 key 有序排列加速查询速度:name 列:唯一索引 => 主键 非唯一索引按 key 有序排列提升查询性能:age 列:索引+主键 => 空值
1.2 TiKV 侧 MVCC 版本写入
t_{table-id}_r1=> start_ts,primary_key,ttl,PUT t_{table-id}_i{indexID}_Bob=>start_ts,primary_key,ttl,PUT t_{table-id}_i{indexID}_12_1=>start_ts,primary_key,ttl,PUT
t_{table-id}_r1_{start_ts}=> {Bob,12,99} t_{table-id}_i{indexID}_Bob_{start_ts}=>1 t_{table-id}_i{indexID}_12_1{start_ts}=>null
t_{table_id}_{commit_ts}=>start_ts
t_{table-id}_r1=>start_ts,primary_key,ttl,PUT





tidb slow log: scan_detail: {total_process_keys: 1139428, total_process_keys_size: 433849330, total_keys: 1139434, rocksdb: {delete_skipped_count: 0, key_skipped_count: 2278852,....
total_process_keys:本次查询扫描的有用的用户 key 个数。不包含已删除的版本及 rocksdb 里面 tombstone 的版本 total_keys:本次查询总共扫的 mvcc 版本个数
delete_skipped_count: rocksdb 里面被标记为删除的 key. 当这个值比较大时,意味着 rocksdb 需要做 compaction 了 key_skipped_count(https://github.com/facebook/rocksdb/blob/9f1c84ca471d8b1ad7be9f3eebfc2c7e07dfd7a7/include/rocksdb/perf_context.h#L84):实际读取过程中,rocksdb 读取迭代器中执行 next 的个数,代表着实际数据的读取量

total_process_keys 是 0,因为它是一张空表。 total_keys =1 因为我们在查询之前并不知道这张表是否为空,需要拿出符合条件的第一个 MVCC 版本才能确认这条 mvcc 不是本表数据。

total_process_keys =1, 表中当前一共有一行数据(id=1) total_keys =2, 扫完 id=1 的 key 后,还要往后扫一个 key 才能确认此表中已经没有数据

total_process_keys =1 因为确实这张表中只有一行数据 total_keys = 3, 因为 id=1 这行数据有两个版本, 也就是本次更新增加了一个版本

total_process_keys =0:删除了 id=1 这行数据后,表里面没有数据了 total_keys = 3+1:而删除 id=1 给这行数据增加了一个版本,所以 total_keys 比上一次多了 1 个


Table scan:代表着按 table 主键查询 Index scan:代表着按索引查询
processed_keys:代表查询后实际用户可见的 key 个数,与 slow-log 中的 total_processed_keys 概念一致 next/seek/..:代表本次查询在 TiKV 迭代器中每个指令的调用次数,一般 next 居多。所有指令总调用次数接近于 slow log 里面 total_keys

tiup ctl:v6.5.0 tikv --host 127.0.0.1:20160 region-properties -r 6493
Starting component `ctl`: /home/tidb/.tiup/components/ctl/v6.5.0/ctl tikv --host 127.0.0.1:20160 region-properties -r 6493
mvcc.min_ts: 442383585748713474
mvcc.max_ts: 442383589195644931
mvcc.num_rows: 410870
mvcc.num_puts: 410870
mvcc.num_deletes: 0
mvcc.num_versions: 410870
mvcc.max_row_versions: 1
writecf.num_entries: 410870
writecf.num_deletes: 0
writecf.num_files: 1
writecf.sst_files: 053983.sst
defaultcf.num_entries: 0
defaultcf.num_files: 0
defaultcf.sst_files:
region.start_key: 7480000000000000ffe75f728000000000ff3f028e0000000000fa
region.end_key: 7480000000000000ffe75f728000000000ff454f410000000000fa
region.middle_key_by_approximate_size: 7480000000000000ffe75f728000000000ff42250e0000000000faf9dc567895dbfffe
mvcc.min_ts:这个 region 里面的所有版本中最小(最老)的 tso mvcc.min_ts:本 region 数据中最新的 mvcc 版本 的 tso mvcc.num_rows:用户可见的 key 个数(包含已删除的)= mvcc.num_put+mvcc.num_delete mvcc.num_put:用户可见的 key 个数(不包含已删除的) mvcc.num_delete:用户可见的已删除的 key 数 mvcc.num_version:用户可见的 mvcc 版本个数 mvcc.max_row_versions:本 region 中版本数最多的那个 key 拥有的版本数量

文章转载自PingCAP,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。










