故事背景

前段时间上线了一个从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_timetidb_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数据库开发规范的重要性的更多相关文章

  1. 使用Unity3D的设计思想实现一个简单的C#赛车游戏场景

    最近看了看一个C#游戏开发的公开课,在该公开课中使用面向对象思想与Unity3D游戏开发思想结合的方式,对一个简单的赛车游戏场景进行了实现.原本在C#中很方便地就可以完成的一个小场景,使用Unity3 ...

  2. 记一次简单的Oracle离线数据迁移至TiDB过程

    背景 最近在支持一个从Oracle转TiDB的项目,为方便应用端兼容性测试需要把Oracle测试环境的库表结构和数据同步到TiDB中,由于数据量并不大,所以怎么方便怎么来,这里使用CSV导出导入的方式 ...

  3. 简单的将Excel数据同步到SqlServer数据库中

    1.创建一个WinForm程序,添加一个Button控件 2.Button事件 private void button1_Click(object sender, EventArgs e) { Sys ...

  4. 利用angular4和nodejs-express构建一个简单的网站(一)——构建前后端开发环境

    学习了一段时间的angular4知识,结合以前自学的nodejs-express后端框架知识,做了一个利用angular4作为前端,node-express作为后端服务器的网站.这个网站的功能很简单, ...

  5. 一个简单的java项目使用hibernate连接mysql数据库

    实体类与表对应文件Customer.hbm.xml <?xml version="1.0" encoding="UTF-8"?><!DOCTY ...

  6. ubuntu 下搭建一个python3的虚拟环境(用于django配合postgresql数据库开发)

     #安装python pip  (在物理环境中安装) sudo apt-get install python-pip       sudo apt-get install python3-pipsud ...

  7. 单表60亿记录等大数据场景的MySQL优化和运维之道

    此文是根据杨尚刚在[QCON高可用架构群]中,针对MySQL在单表海量记录等场景下,业界广泛关注的MySQL问题的经验分享整理而成,转发请注明出处. 杨尚刚,美图公司数据库高级DBA,负责美图后端数据 ...

  8. 【转】单表60亿记录等大数据场景的MySQL优化和运维之道 | 高可用架构

    此文是根据杨尚刚在[QCON高可用架构群]中,针对MySQL在单表海量记录等场景下,业界广泛关注的MySQL问题的经验分享整理而成,转发请注明出处. 杨尚刚,美图公司数据库高级DBA,负责美图后端数据 ...

  9. [转载] 单表60亿记录等大数据场景的MySQL优化和运维之道 | 高可用架构

    原文: http://mp.weixin.qq.com/s?__biz=MzAwMDU1MTE1OQ==&mid=209406532&idx=1&sn=2e9b0cc02bdd ...

随机推荐

  1. 【Spring】重新认识 IoC

    前言 IoC (Inversion of control) 并不是Spring特有的概念. IoC 维基百科的解释: In software engineering, inversion of con ...

  2. 【UE4 调试】C++ 几种编译方法和小技巧

    编译方法 Visual Studio 2019 编译 默认编译 UnrealVS 快速编译 Editor 编译 一般 vs 编译完后,Editor会跟着热编译(有声音) 如果发现编译后代码没更新到Ed ...

  3. 敏捷 Scrum Master 的難點

    什麼是 Scrum Master? Scrum master 是一個團隊角色,負責確保團隊遵守敏捷方法和原則並符合團隊的流程和實踐. Scrum Master 促進敏捷開發團隊成員之間的協作.Scru ...

  4. 记一个非常诡异的关于 shared_ptr 的 bug

    问题描述 今天写项目的时候遇见一个特别诡异的 bug,体现在在执行某条语句时,程序会莫名崩溃,并且给出的错误信息也非常难懂,只有一个malloc(): invalid size (unsorted)错 ...

  5. UltraSoft - Beta - Scrum Meeting 11

    Date: May 27th, 2020. Scrum 情况汇报 进度情况 组员 负责 今日进度 q2l PM.后端 记录会议 Liuzh 前端 增加了对重复日程的支持 Kkkk 前端 测试验证前后端 ...

  6. [Beta]the Agiles Scrum Meeting 5

    会议时间:2020.5.19 20:00 1.每个人的工作 今天已完成的工作 成员 已完成的工作 issue yjy 为评测机增加更多评测指标 评测部分增加更多评测指标 tq 为评测机增加更多评测指标 ...

  7. Beta阶段第三次会议

    Beta阶段第三次会议 完成工作 姓名 工作 难度 完成度 ltx 1.掌握小程序代码和相关知识2.构思小程序游客模式 轻 90% xyq 1.修改场地表格信息2.对原页面活动申请场地部分进行修改 轻 ...

  8. mongodb的索引操作

    在mongodb中,当我们一个集合中的数据量非常大时,比如几百万条数据,如果不使用索引,对数据的查询就会进行全表扫描,这个时候查询的速度就会非常的慢,此时我们就需要为集合建立上索引,从而加快查询的速度 ...

  9. BZOJ4919[Lydsy1706月赛]大根堆-------------线段树进阶

    是不是每做道线段树进阶都要写个题解..根本不会写 Description 给定一棵n个节点的有根树,编号依次为1到n,其中1号点为根节点.每个点有一个权值v_i. 你需要将这棵树转化成一个大根堆.确切 ...

  10. best-time-to-buy-and-sell-stock-ii leetcode C++

    Say you have an array for which the i th element is the price of a given stock on day i. Design an a ...