QCon 全球软件开发大会(北京站)门票 9 折倒计时 4 天,点击立减 ¥880 了解详情
写点什么

TiDB MVCC 多版本保存机制及其对性能的影响

2019 年 11 月 04 日

TiDB MVCC 多版本保存机制及其对性能的影响

从接触 TiDB 以来,就看到过 TiDB 官方文档上的提示,gc_life_time 设置过大,会因为历史版本过多,影响查询效率,但是为什么 SQL 非要去扫描历史版本呢?下面列举一些知识点一步一步来解析这个问题


1. TiDB key 的编码方式

TiDB 会对每个表分配一个全局唯一的 table_id,每一个索引都会分配一个表内唯一的 index_id,每一行分配一个 row_id(如果表有整数型的 Primary Key,那么会用 Primary Key 的值当做 row_id,如果没有,那么 TiDB 会自动生成一个隐式主键_tidb_rowid)


数据编码方式:


t{table_id}_r{row_id}-->[col1,col2,col3,...]


索引编码方式:


unique index


t{table_id}_i{index_id}_{index_column_value}-->[row_id]


非 unique index


t{table_id}_i{index_id}_{index_column_value}_{row_id}-->null


举个栗子:


CREATE TABLE `test_table` (  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主键',  `column1` varchar(10) DEFAULT NULL,  `column2` varchar(10) DEFAULT NULL,  PRIMARY KEY (`id`),  KEY `idx_column1` (`column1`)
复制代码


idcolumn1column2
1“a”“b”
2“c”“d”


那么主键编码可以抽象为


t10_r1-->[1,"a","b"]


t10_r2-->[2,"c","d"]


索引idx_column1编码可以抽象为


t10_i1_a_1-->null


t10_i1_b_2-->null


2. MVCC 多版本信息是如何保存的?

TiDB 使用基于 Percolator 的事务模型,将一行数据抽象为 default、write 和 lock 3 个 CF(column family)存储,其中:


  • default CF 存储的真正数据

  • ${key}_${start_ts} --> ${value}

  • write CF 存储数据的版本信息,commit_ts 代表一行记录的真正版本

  • ${key}_${commit_ts}-->${start_ts}

  • lock CF 存放锁信息,提交中的事务会加 lock,包含 primary lock 的位置

  • ${key}-->${start_ts,primary_key,..etc}


一个读取操作的过程如下:


  1. 事务 begin 时,从 PD 获取 start_ts

  2. 读取 key,先判断 lock CF 有没有锁,如果:

  3. a. 有锁

  4. 判断 primary key 状态是否超时

  5. 若锁未超时,等待

  6. 若锁已超时,根据 primary key 的状态 rollback 或 commit 残留事务

  7. b. 无锁

  8. 根据当前事务获取的 start_ts 对比数据的 commit_ts(write CF)

  9. start_ts 大于 commit_ts,返回这行数据

  10. start_ts 小于 commit_ts,继续查找当前行的下一个(更早的)版本

  11. 根据步骤 2 得到的 commit_ts,从 default CF 中获取真正的数据


TiDB 将一行记录的多个版本按照从新到老的顺序排列,这样方便我们获得满足查询条件的最新记录,TiKV 默认存储引擎是 RocksDB,RocksDB 一个 seek 操作要比 next 操作昂贵很多,如下这个例子


假设 key1,key2,key3 都有多次更新,生成的 mvcc 版本从老到新分别为 key1_v1,key1_v2,key1_v3,key1_v4…


几个相邻 key 的存放方式抽象为下图:



Rocksdb 没办法精确去定位每一个 key,如果扫描每一个 key 都走 seek 接口,这样代价太大。所以如图,假设一个范围查询 seek 到第一个 key,key1 之后,就开始调用 next 函数获取后面的 key2、key3 值,这样需要遍历 key1 甚至 key2 的所有历史版本。如此,就能解释为什么过多的历史版本会让查询效率急剧下降了。


我们日常工作经常碰见的几个问题:


一、SQL 执行时间不稳定 慢日志中会发现这些 SQL 语句的 total keys 比 process keys 大很多,这就是典型的历史版本过多,导致扫描了大量历史数据。解决方法


  1. 减小 gc_life_time,或者让业务缩小查询范围。

  2. 升级 TiDB3.0 版本 TiDB3.0 之前的版本,全局 GC 效率不高,容易积压大量历史版本数据。3.0 之后改成了分布式 GC,能够快速释放大量已删除的历史版本,再加上更完善的 region merge 功能,会让整个集群的性能提升一个很大的台阶。


二、删除 &归档特定日期以前的记录


while True:    delete table where {$condition} limit n    if affectrows==0:        break
复制代码


这个场景的现象是 delete 语句会越来越慢。因为扫描范围{$condition}是固定不变的,delete 删除语句在 TiDB 处理方式是标记删除,删除本身实际上也是插入一条 kv 记录,只不过 value 变成了 delete,最后通过逻辑 GC 和 compaction 来删除真实数据。所以,循环执行 delete 语句,每次删除 n 条记录,下一次 delete 语句要扫描的 key 就会+n,执行时间越来越长(大家可以去做个实验,观察慢日志文件,同样的 delete 语句 total keys 会不断增加)。


那么,怎样去删除 &归档特定日期前的记录比较高效呢?


首先,我们知道 TiDB 对事务大小是有限制的


  1. 单个事务包含的 SQL 语句不超过 5000 条

  2. 操作的单条记录不超过 6MB

  3. 事务操作的总 keys 不超过 30w

  4. 事务操作的所有记录总大小不超过 100MB


由于 TiDB 的事务限制和 TiDB mvcc 的实现原理,想要删除 &归档一个特定范围的数据,目前没有太好的方法。整理一些个人心得供大家参考:


第一种方式:


尽量缩小范围删除的粒度,比如提前按分钟将数据分段,打开 tidb_batch_delete,提高并发去删除。注意使用开闭区间,分段之间不要出现冲突,TiDB 解决事务冲突的代价比较大。


set @@session.tidb_batch_delete=1;delete from table where create_time > '$start_step' and create_time <= '$end_step';
复制代码


如果分段内的数据超出事务大小限制,TiDB 会自动将 delete 操作拆分成多个 batch。个人亲测,这种方式删除数据的速度还是比较快的。


第二种方式:


按照日期分表,删除过期的表即可。TiDB 删表是秒级的,后续空间回收也比较快,缺点是侵入业务。两种方式各有利弊,大家可以各取所需。


作者介绍


吕磊,美团点评 DBA, TiDB User Group (TUG) 大使。


本文转载自 AskTUG


原文链接


https://asktug.com/t/tidb-mvcc/1350/1


2019 年 11 月 04 日 00:001598

评论 1 条评论

发布
用户头像
lock列族的时间戳是在值里面吗?
2021 年 04 月 24 日 21:35
回复
没有更多了
发现更多内容

进程、线程与协程还傻傻分不清?P7大佬大白话讲解,直接秒懂

互联网架构师小马

Java 线程 多线程 进程

JVM性能调优实战:让你的IntelliJ Idea纵享丝滑

Silently9527

Java jvm调优

第一周作业

Castie!

G20210639010067-产品训练营第一次作业

Wangyunnfei

产品经理训练营第一周作业

朱琴

产品经理训练营

spring: 我是如何解决循环依赖的?

互联网架构师小马

Java spring 编程 软件开发 循环依赖

【HTML】<blockquote> 和 <q>

学习委员

CSS html html5 前端 28天写作

《清单革命》读书笔记

BigYoung

读书笔记 读书感悟 清单革命

如果公司要找一个人代替我,该是个咋样的人?

再见陛下

offer

Windows DHCP最佳实践(一)

BigYoung

windows Windows Server 2012 R2 DHCP

产品训练营第一周总结

mayue

产品 总结 产品经理训练营

HDFS杂谈:SnapShot快照

罗小龙

hadoop hdfs 28天写作

产品训练营第一周作业【撰写岗位模型】

mayue

产品 学习笔记 产品经理训练营

产品经理训练营作业 NO.1

郭栋

《追风筝的人》读书笔记

BigYoung

读书笔记 读书感悟 追风筝的人

你还在担心搞不定面试官?吃透这份4000道Java面试真题合集,金三银四的黑马就是你了

云流

Java 编程 程序员 面试

高仿书旗小说 Flutter 版,学起来

GitHub指北

动力电池知识皮毛(28天写作 Day11/28)

mtfelix

自动驾驶 28天写作 电动汽车

特征归一化

IT蜗壳-Tango

机器学习 七日更 特征归一化

28天瞎写的第二百二十二天:五道营胡同的葡萄芽儿

树上

28天写作

第一章作业

大小姐

产品经理训练营-第一章作业

Ryun

你真的知道如何删除list集合中特定元素吗

万里无云

Java List remove

产品经理训练营-第一周作业

月亮 😝

产品经理训练营

第0期产品经理训练营-第一周作业

nobody

产品经理训练营

细说MAC地址漂移

第一期作业

糯米~

产品策略经理岗位要求

赵志广

网络安全 产品安全 产品经理训练营 极客大学产品经理训练营

产品 0 期 - 第一周作业

vipyinzhiwei

产品经理训练营

产品经理学习第一次作业

海风涯

智能合约业务场景探索(二)

石君

智能合约 28天写作

边缘计算隔离技术的挑战与实践

边缘计算隔离技术的挑战与实践

TiDB MVCC 多版本保存机制及其对性能的影响-InfoQ