写点什么

使用 tidb-toolkit 批量删除 / 更新数据

  • 2023-08-18
    北京
  • 本文字数:2636 字

    阅读完需:约 9 分钟

作者: realcp1018 原文来源:https://tidb.net/blog/2056c25c

一、背景简介

在日常使用 TiDB 的场景中经常会遇到此类问题:


  1. 建表时未使用分区表,导致数据删除困难。

  2. 想要快速的批量更新数据,但是每次都需要编写繁杂的跑批任务。

  3. 想要在数亿、数十亿的大表中删除几千完或几百万数据但是却总是遇到“事务大小超出限制”的错误。


为解决此类问题我编写了一个通用的删除工具,用于处理此类“事务大小超出限制”的错误。可类比 mysql 的 oak-chunk-update 工具。这个工具在我司生产环境运行两年以来,高效的删除了大量数据,目前已趋于完善,因此写笔记分享出来。


项目地址:https://github.com/realcp1018/tidb-toolkit

二、环境要求

git clone https://github.com/realcp1018/tidb-toolkit.git
复制代码


运行 “python3 -m pip install -r requirements.txt” 安装 python3 库依赖.


将项目目录添加至 $PYTHONPATH:


# 假设项目被clone至/data目录下:  export PYTHONPATH=$PYTHONPATH:/data/tidb-toolkit  
复制代码

三、使用示例

1. 使用 tk_dml_byid 对表执行 “delete from where …” (表必须未设置 auto_random 或 shard_rowid_bits,如果误在此类表上运行也关系,只是效率极底)


# 编辑 tidb.toml 的 [basic], [dml] 和 [dml.by_id] 部分,其他部分的设置不影响本次运行  db = "test"  table = "tb1kb_1"  sql = "delete from tb1kb_1 where is_active=0;"  execute = false  # 运行:  python3 scripts/tk_dml_byid.py -f conf/tidb.toml -l tb1kb_1.log  # execute = false: 设置此项表示不实际进行数据删除,仅打印一个拆分后的示例SQL,适用于比较谨慎的场景  # 确保输出的拆分SQL符合预期,然后可以修改为true实际运行  
复制代码


2. 使用 tk_dml_by_time 对表执行 “delete from where …” (表已设置 auto_random 或 shard_rowid_bits,或者仅仅想根据时间列删除极少部分数据)


# 编辑 tidb.toml 的 [basic], [dml] and [dml.by_time] 部分  db = "test"  table = "tb1kb_1"  sql = "delete from tb1kb_1 where is_active=0;"  # 假设 create_time 类型为 int(时间精度为ms)  split_column = "create_time"  split_column_precision = 3  split_interval = 3600  start_time = "2021-01-01 00:00:00"  end_time = "2021-12-31 00:00:00"  execute = false  # 运行:  python3 scripts/tk_dml_by_time.py -f conf/tidb.toml -l tb1kb_1.log  
复制代码


3. 使用 tk_chunk_update 对表执行 “delete from where …” (可覆盖上述两种场景,无需考虑表是否设置 auto_random 或 shard_rowid_bits)


# 编辑 tidb.toml 的 [basic], [dml] 和 [dml.chunk_update] 部分  db = "test"  table = "tb1kb_1"  sql = "delete from tb1kb_1 where is_active=0;"  execute = false  # 运行:  python3 scripts/tk_chunk_update.py -f conf/tidb.toml -l tb1kb_1.log  # execute = false  # 确保输出的拆分SQL符合预期,然后可以修改为true实际运行  
复制代码

四、常见问题

1. 支持哪些类型的 SQL?


工具支持以下几种 SQL 类型:


1.delete from <table> where <...>  2.update <table> set <...> where <...>  3.insert into <target_table> select <...> from <source_table> where <...>  
复制代码


非 DML 类型会直接报错退出。


2. 如何在 tk_chunk_update 和 tk_dml_by_id, tk_dml_by_time 之间做出选择?


tk_chunk_update 无需人为进行表的类型判断,适用性高,只要是 tidb 的表都可以使用。相比 tk_dml_by_id 可以避免大量无效 rowid 扫描,相比 tk_dml_by_time 则单条 SQL 执行更快。


但 tk_chunk_update 有极底概率遇到性能衰退的情况:如果表包含大量空 region(或 gc 缓慢),那么在执行日志中你会看到如下记录:


ConnectionPool Monitor: Size 99以及:chunk xxx Done [split_time=0:00:00.523525] [duration=0:00:00.229860] [rows=1000] [sql=...]


可以看到 split_time 大于 duration, 这意味着 chunk 的生产速度慢于消费速度, 而 tk_chunk_update 的要点之一是就是需要保证 chunk 的生产速度远大于消费速度。


其原理如下, 首先 ChunkSpliter.split() 通过如下查询条件获取 chunk 的右边界:


select max(rowid) from      (select rowid from table_name where rowid > current_rowid order by rowid limit 0,chunk_size) t  
复制代码


并以 current_rowid 作为 chunk 左边界,然后生成拆分后的 SQL,输出一个 Chunk 同时将 current_rowid 推进至 chunk 右边界以便下一个 chunk 拆分使用,而 split() 是一个 python 生成器。


然后 Executor 遍历 split() 生成的 chunk,调用其 execute 方法并将其作为一个 future 放入 ThreadPoolExecutor 中 (执行并发度为 max_workers)。


这个机制要求 chunk 的生成速度大于消费速度,否则会衰退为单线程执行,但是为目前了规避 by_id 的缺点又必须采用这种动态生成的方式,因此当你遇到此类性能衰退时, 请使用 tk_dml_by_id/tk_dml_by_time.


3. 关于 tk_dml_byid.py 和 tk_dml_bytime.py 的拆分方式


By id:


  • 默认使用 rowid 作为拆分列 (如官网所示,数字类型主键就是 rowid,其他情况则有一个内置的 _tidb_rowid 作为 rowid)

  • 如果表设置了 (SHARD_ROW_ID_BITS 或 auto_random), 那么建议使用 tk_dml_bytime 或 tk_chunk_update.

  • SQL 的拆分方式很简单,直接按 rowid 累加 batch_size 拆分为无数个 batch(例如rowid >= 1 and rowid < 1000), 并发执行度为 <max_workers>.


By time:


  • 与 by id 的拆分方式相似,但是是通过时间列拆分为无数个 task,拆分单位为配置文件中的 split_interval

  • 执行方式与 by id 略有不同,因为按时间列拆分后的 task 内部可能包含的记录数扔超出事务限制,因此实际上在 task 内部会以 batch_size 为单位顺序执行同一条分页 SQL 直到影响行数为 0

  • 通过 grep Finished <log-name> | tail 可以看到有多少 task 已完成


3. 关于 tk_chunk_update.py 的执行进度查看


通过如下命令可以查看当前任务的执行进度:


tailf <log-file>|grep "write savepoint"


4. 关于 savepoint 和失败重试


tk_dml_by_id.py 和 tk_chunk_update.py 在执行过程中会生产检查点 (如未设置 savepoint 配置则会生成一个默认的检查点文件),检查点表示在这之前已经处理完毕的 rowid。无论是异常退出还是主动终止,再次运行时如果检查点文件存在则会跳过已处理的 rowid.tk_dml_by_time.py 则 不会 产生检查点,如果任务失败建议查看执行日志手动设置一个 start_time 然后重跑,通常来说只需要选一个相对较早的时间即可。


发布于: 刚刚阅读数: 3
用户头像

TiDB 社区官网:https://tidb.net/ 2021-12-15 加入

TiDB 社区干货传送门是由 TiDB 社区中布道师组委会自发组织的 TiDB 社区优质内容对外宣布的栏目,旨在加深 TiDBer 之间的交流和学习。一起构建有爱、互助、共创共建的 TiDB 社区 https://tidb.net/

评论

发布
暂无评论
使用tidb-toolkit批量删除/更新数据_性能调优_TiDB 社区干货传送门_InfoQ写作社区