故事背景

前段时间上线了一个从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. GPIO位带操作点亮LED,且使用按键控制开关

    1. 项目 类似与C51单片机的位操作使能引脚来点亮LED. 例如,sbit P0^0 = 0 LED1 = P0^0; 2. 代码 main.c #include "stm32f10x.h ...

  2. Redis:学习笔记-03

    Redis:学习笔记-03 该部分内容,参考了 bilibili 上讲解 Redis 中,观看数最多的课程 Redis最新超详细版教程通俗易懂,来自 UP主 遇见狂神说 7. Redis配置文件 启动 ...

  3. 【技术博客】Flutter—使用网络请求的页面搭建流程、State生命周期、一些组件的应用

    Flutter-使用网络请求的页面搭建流程.State生命周期.一些组件的应用 使用网络请求的页面搭建流程 ​ 在开发APP时,我们常常会遇到如下场景:进入一个页面后,要先进行网络调用,然后使用调用返 ...

  4. BUAA_2020_软件工程_提问回顾与总结

    项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任建) 这个作业的要求在哪里 提问回顾与总结作业要求 我在这个课程的目标 了解软件工程的技术,掌握工程化开发的能力 这个作业在哪 ...

  5. Noip模拟58 2021.9.21(中秋祭&&换机房祭)

    第一次在学校过中秋节,给家里人视频电话,感觉快回家了很开心, 然后还吃了汉堡喝饮料非常爽,颓废了一会儿还换了新机房,$Linux2.0$非常dei,少爷机也非常快, 发现好像测评机又成了老爷机,这就是 ...

  6. 2021.10.10考试总结[NOIP模拟73]

    T1 小L的疑惑 对于\(P_i\),如果所有比\(P_i\)小的数加起来也达不到\(P_i-1\),那么值域肯定不连续.否则设原来值域最大值为\(mx\),则\(P_i\)会让值域最大值增致\(mx ...

  7. 转:基于 xilinx vivado 的PCIE ip核设置与例程代码详解

    连接:https://blog.csdn.net/u014586651/article/details/103826967#comments

  8. hdu 3032 Nim or not Nim? (SG,然后找规律)

    题意: n堆石头,每堆石头个数:s[1]...s[n]. 每人每次可以选择在一堆中取若干个(不能不取),或者把一堆石头分成两堆(两堆要都有石头). 无法操作者负. 数据范围: (1 ≤ N ≤ 10^ ...

  9. virt-v2v命令将ESXI 虚机迁移到OpenStack中

    一简介: virt-v2v是将外部的虚拟化平台上的虚拟机转化到可以运行的KVM平台上.它可以读取在VMware.Xen运行Hyper-V和其他虚拟机管理程序上的Windows和Linux的虚拟机,并将 ...

  10. Jetbrains 系 IDE 编辑器的代码提示功能

    著名的 Jetbrains 可谓编程界的一大福音,众多有名代码编辑器比如 ItelliJ IDEA.PHPStorm.WebStorm.PyCharm 等,均出自这家公司麾下. 对于中国的Java开发 ...