从一个简单的Delete删数据场景谈TiDB数据库开发规范的重要性
故事背景
前段时间上线了一个从Oracle迁移到TiDB的项目,某一天应用端反馈有一个诡异的现象,就是有张小表做全表delete的时候执行比较慢,而且有越来越慢的迹象。这个表每次删除的数据不超过20行,那为啥删20行数据会这么慢呢,我们来一探究竟。
问题排查
根据应用端提供的表名去慢查询里面搜索,确实发现了大量全表删除的SQL:

从列表中找一条来看看具体的时间分布:

可以发现绝大部分时间都花了Coprocessor阶段,这个阶段表示请求已经被下推到了TiKV执行,我们继续看看在TiKV里面都做了些什么。一看吓一跳,一个很“小”表的删除竟然会扫描了成千上万个key:

这一点我们也可以从执行计划中得出结论,时间几乎都花在了数据扫描上面:

到这里为止基本就能判断出慢的原因就在于扫描了很多无效的key,上面这个例子最终删除的数据只有9行,但是却扫描了近80万个key,很明显这是由GC引发的一个惨案,因为这个集群中gc_life_time设置的是48h。至于为什么要设置这么大,其中的故事我们不去讨论。
问题似乎很简单,但是这里面涉及到的知识点很多也非常重要,我觉得有必要做一次系统梳理,防止新手踩坑。
删数据的原理解析
要搞清楚删除数据的原理,有几个东西你必须要知道:
TiDB的GC和MVCC
Region的概念以及Key的构成
熟悉TiDB的朋友都知道,TiKV底层是直接使用Rocksdb来存储kv数据,而Rocksdb使用的是LSM tree这种数据结构,它是一种append only模型,也就是说所有对数据的变更都体现在追加上。
这是什么意思呢?比如说对一行数据做update,体现在存储上的并不是找到原来的数据直接更新,而是新增一行数据,同时把原来的数据标记为旧版本,这些历史版本就构成了MVCC,同理delete也是一样,并不是直接把原数据删了,而是一种逻辑删除。
那究竟要保留多少历史版本,如何去清理这些历史版本呢,这个就是由GC单元去处理。系统变量tidb_gc_life_time和tidb_gc_run_interval可以控制GC的行为,tidb_gc_life_time定义了历史版本保留的时间,tidb_gc_run_interval定义了GC运行的周期,它们默认都是10分钟。
Region是TiDB中对数据进行划分的一种逻辑概念,是数据调度的最小单位,TiDB对数据的分片也体现在Region上。它是由一段连续的key范围组成,我们可以通过如下方式查询某张表由哪些Region组成:

Region里的key是一种有规则的编码,数据和索引都是以如下的方式转换为KV键值对,最终存储在Rocksdb中:

我们可以发现同一张表里的数据,它的key前缀都是相同的,这样就方便对表进行范围查找。
大家有可能看到的startkey和endkey中tableid不是同一个,这种是正常现象,因为对于比较小的表是存在多个表共用一个Region的。
结合前面介绍的GC和Region概念,可以发现如下可能存在的问题(摘自官网文档):
在数据频繁更新的场景下,将 tidb_gc_life_time 的值设置得过大(如数天甚至数月)可能会导致一些潜在的问题,如:
- 占用更多的存储空间。
- 大量的历史数据可能会在一定程度上影响系统性能,尤其是范围的查询(如 select count(*) from t)。
所以说,一旦涉及到范围查询并且没有索引的情况下,GC对性能的影响就非常大。恰巧本文的这个delete整张表场景就是典型的全表扫描,这里的全表扫描指的是扫描这个表包含的所有历史版本key,而不仅仅是当前你能看到的那些数据。因此,对大表千万千万不要这样清数据,它相当于全表扫一遍,再全表写一遍,非常恐怖。
大家是不是普遍认为,我只删9条数据那就扫描这9条数据的key就好了,为什么要扯上那么多无关的key?我也认为应该是这样的,可能实现上有TiDB自己的考虑吧(或许是一个个key去判断效率更慢?)。
既然我们改变不了这个现状,那么如何用正确的方式去删数据就是要重点关心的了。
删数据的最佳实践
实际场景中,删数据不外乎以下几种情况:
- 对某张表按过滤条件批量删除
- 删除某张表的全部数据,俗称清表
- 删表
- 删库
对于第一种,如果结果集很大,最佳做法是把过滤条件进行细化,一批一批的去删。它的好处是首先不容易触发大事务限制,其次能够减少误删的情况。不仅仅是批量删除,批量更新也应该是同样的做法,把条件拆的更细一些。我常用的做法是,按过滤条件找出对应数据行的rowid,然后把这些rowid进行分段,对这一段的范围做更新或删除,这样能极大提升操作效率。
对于第二种全表删除,极力推荐使用truncate,它相当于删表重建新表,所以tableid必然是和以前不一样了,那就肯定不会扫描到历史版本数据,删表建表也只涉及到元数据操作,速度很快。还有一点,truncate数据以后,被GC扫过的历史数据会直接清掉释放出存储空间,delete操作则不会释放,要等到compaction才能被再次利用。
对于第三种,没得选了,只有drop table。
对于第四种,也只有drop database。
那么问题来了,以上几种删数据的方式,万一是误删你想好了如何快速恢复吗?还是想直接paolu。。。
TiDB开发规范
在这个项目中经历过好几次大批量修复数据造成数据库不稳定的情况,因为这个系统的开发者和DBA都是Oracle背景,他们习惯了一上来就一条SQL对上亿的大表做批量操作,这显然在TiDB中不太适用,动不动就是SQL OOM或者各种too large,再就是导致CPU和内存飙升。
我觉得TiDB开发规范在早期的技术选型中就应该是要被重点考虑的一环,要充分了解TiDB的使用方式和限制条件是否能被开发运维团队接受。确定使用TiDB以后,开发和运维人员还要继续去落实执行,特别是一些高频使用场景,这样才能达到事半功倍的效果。
就比如常见的加索引,TiDB在有了数据以后加索引是特别慢的,而且是个串行操作。如果你发现有个join查询特别慢,需要给两张表分别加上索引,是马上就加吗,先加哪一个,加几个合适?
社区里有一篇非常全的开发规范说明值得每一位去细读,希望大家都能收藏,时不时翻出来看看。
https://asktug.com/t/topic/93819
总结
本文提到的场景只是这个项目中的一个缩影,因为项目周期原因应用端很多不好的SQL(你能想象到还有where or几千个条件?)都没有来得及优化,所以暴露出了正确使用TiDB的重要性。
没有绝对完美的产品,我们要充分了解它的原理,使用的时候做到扬长避短,这样既能发挥它的价值也能提升我们的效率。
从一个简单的Delete删数据场景谈TiDB数据库开发规范的重要性的更多相关文章
- 使用Unity3D的设计思想实现一个简单的C#赛车游戏场景
最近看了看一个C#游戏开发的公开课,在该公开课中使用面向对象思想与Unity3D游戏开发思想结合的方式,对一个简单的赛车游戏场景进行了实现.原本在C#中很方便地就可以完成的一个小场景,使用Unity3 ...
- 记一次简单的Oracle离线数据迁移至TiDB过程
背景 最近在支持一个从Oracle转TiDB的项目,为方便应用端兼容性测试需要把Oracle测试环境的库表结构和数据同步到TiDB中,由于数据量并不大,所以怎么方便怎么来,这里使用CSV导出导入的方式 ...
- 简单的将Excel数据同步到SqlServer数据库中
1.创建一个WinForm程序,添加一个Button控件 2.Button事件 private void button1_Click(object sender, EventArgs e) { Sys ...
- 利用angular4和nodejs-express构建一个简单的网站(一)——构建前后端开发环境
学习了一段时间的angular4知识,结合以前自学的nodejs-express后端框架知识,做了一个利用angular4作为前端,node-express作为后端服务器的网站.这个网站的功能很简单, ...
- 一个简单的java项目使用hibernate连接mysql数据库
实体类与表对应文件Customer.hbm.xml <?xml version="1.0" encoding="UTF-8"?><!DOCTY ...
- ubuntu 下搭建一个python3的虚拟环境(用于django配合postgresql数据库开发)
#安装python pip (在物理环境中安装) sudo apt-get install python-pip sudo apt-get install python3-pipsud ...
- 单表60亿记录等大数据场景的MySQL优化和运维之道
此文是根据杨尚刚在[QCON高可用架构群]中,针对MySQL在单表海量记录等场景下,业界广泛关注的MySQL问题的经验分享整理而成,转发请注明出处. 杨尚刚,美图公司数据库高级DBA,负责美图后端数据 ...
- 【转】单表60亿记录等大数据场景的MySQL优化和运维之道 | 高可用架构
此文是根据杨尚刚在[QCON高可用架构群]中,针对MySQL在单表海量记录等场景下,业界广泛关注的MySQL问题的经验分享整理而成,转发请注明出处. 杨尚刚,美图公司数据库高级DBA,负责美图后端数据 ...
- [转载] 单表60亿记录等大数据场景的MySQL优化和运维之道 | 高可用架构
原文: http://mp.weixin.qq.com/s?__biz=MzAwMDU1MTE1OQ==&mid=209406532&idx=1&sn=2e9b0cc02bdd ...
随机推荐
- netty系列之:使用netty实现支持http2的服务器
目录 简介 基本流程 CleartextHttp2ServerUpgradeHandler Http2ConnectionHandler 总结 简介 上一篇文章中,我们提到了如何在netty中配置TL ...
- ES2020新特性记录
1.可选链操作符 // oldlet ret = obj && obj.first && obj.first.second// newlet ret = obj?.fi ...
- 【UE4 C++ 基础知识】<1> UPROPERTY宏、属性说明符、元数据说明符
属性声明 属性使用标准的C++变量语法声明,前面用UPROPERTY宏来定义属性元数据和变量说明符. UPROPERTY([specifier, specifier, ...], [meta(key= ...
- Python绘制Excel图表
今天讲解下如何使用Python绘制各种Excel图表,下面我们以绘制饼状图.柱状图.水平图.气泡图.2D面积图.3D面积图为例来说明. import openpyxlfrom openpyxl imp ...
- ScatterLayout:分散布局在py中的引用
""" ScatterLayout:分散布局 """ from kivy.app import App from kivy.uix.scat ...
- LeetCode:并查集
并查集 这部分主要是学习了 labuladong 公众号中对于并查集的讲解,文章链接如下: Union-Find 并查集算法详解 Union-Find 算法怎么应用? 概述 并查集用于解决图论中「动态 ...
- LeetCode:回溯算法
回溯算法 这部分主要是学习了 labuladong 公众号中对于回溯算法的讲解 刷了些 leetcode 题,在此做一些记录,不然没几天就忘光光了 总结 概述 回溯是 DFS 中的一种技巧.回溯法采用 ...
- Beta-功能规格说明书
项目 内容 这个作业属于哪个课程 2021春季软件工程(罗杰 任健) 这个作业的要求在哪里 团队项目-计划-功能规格说明书 一.引言 1. 项目简介 项目团队:删库跑路对不队 项目名称:题士 项目内容 ...
- 替换excel模板中的内容并使用JavaMail发送邮件
由于在公司工作,常年出差,每天都要以日报的形式向公司汇报当天的工作内容.而日报的内容大体上就只有当天工作的主要内容时变化的,其余的都是不变 的. 而我的电脑刚打开excel有点卡,因此决定使用Java ...
- 自定义注解结合切面和spel表达式
在我们的实际开发中可能存在这么一种情况,当方法参数中的某些条件成立的时候,需要执行一些逻辑处理,比如输出日志.而这些代码可能都是差不多的,那么这个时候就可以结合自定义注解加上切面加上spel表达式进行 ...