使用 tidb-toolkit 批量删除 / 更新数据
作者: realcp1018 原文来源:https://tidb.net/blog/2056c25c
一、背景简介
在日常使用 TiDB 的场景中经常会遇到此类问题:
建表时未使用分区表,导致数据删除困难。
想要快速的批量更新数据,但是每次都需要编写繁杂的跑批任务。
想要在数亿、数十亿的大表中删除几千完或几百万数据但是却总是遇到“事务大小超出限制”的错误。
为解决此类问题我编写了一个通用的删除工具,用于处理此类“事务大小超出限制”的错误。可类比 mysql 的 oak-chunk-update 工具。这个工具在我司生产环境运行两年以来,高效的删除了大量数据,目前已趋于完善,因此写笔记分享出来。
项目地址:https://github.com/realcp1018/tidb-toolkit
二、环境要求
运行 “python3 -m pip install -r requirements.txt” 安装 python3 库依赖.
将项目目录添加至 $PYTHONPATH:
三、使用示例
1. 使用 tk_dml_byid 对表执行 “delete from where …” (表必须未设置 auto_random 或 shard_rowid_bits,如果误在此类表上运行也关系,只是效率极底)
2. 使用 tk_dml_by_time 对表执行 “delete from where …” (表已设置 auto_random 或 shard_rowid_bits,或者仅仅想根据时间列删除极少部分数据)
3. 使用 tk_chunk_update 对表执行 “delete from where …” (可覆盖上述两种场景,无需考虑表是否设置 auto_random 或 shard_rowid_bits)
四、常见问题
1. 支持哪些类型的 SQL?
工具支持以下几种 SQL 类型:
非 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 的右边界:
并以 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 然后重跑,通常来说只需要选一个相对较早的时间即可。
版权声明: 本文为 InfoQ 作者【TiDB 社区干货传送门】的原创文章。
原文链接:【http://xie.infoq.cn/article/ad08490dd16fd96bf6be767fe】。文章转载请联系作者。
评论