TiDB Lightning 导入超大型 txt 文件实践
原文来源:https://tidb.net/blog/76a75306
背景
TiDB 提供了很多种数据迁移的方式,但这些工具 / 方案普遍对 MySQL 比较友好,一旦涉及到异构数据迁移,就不得不另寻出路,借助各种开源或商业的数据同步工具。其实数据在不同系统的流转当中,有一种格式是比较通用的,那就是 txt/csv 这类文件,把数据用约定好的分隔符换行符等标记存放在一起,比如最常见的逗号分隔:
这个文件可以保存为data.txt
或者data.csv
,一般主流的数据库都支持把这类文件直接导入到对应的表中。
csv 本身就是逗号分隔符文件,但是由于逗号太常见了很容易和真实数据混淆,往往会用比较复杂的字符作为分隔符,这时候 txt 文件就更灵活一些。
在 TiDB 中我们想导入 csv 文件可以选择的方式有Load Data
和Lightning
,但是从官方文档得知,这两种方式都没有明确表示支持 txt 文件导入。但是经过实测,实际上都能够支持 txt 格式文件,Load Data
参考 csv 导入即可,本文重点介绍Lightning
如何导入 txt 数据,毕竟数据量很大的时候还得靠Lightning
。
有人可能会质疑,不就是改个文件扩展名就能解决的问题何必搞得这么麻烦,要知道有些时候用户并不接受把 txt 强制改成 csv,担心有损坏数据风险。。
咱也不敢说咱也不敢问,只能默默研究 lightning。
Lightning 导入简单的 txt 文件
虽然官网文档明确表示 TiDB Lightning 支持以下文件类型:
Dumpling 生成的文件
CSV 文件
但并没有说不支持 txt,这就会让人抱有一丝幻想,尝试用默认的方式导入 txt:
如果这样运行 Lightning 你会发现并不会有任何报错信息,甚至日志最后还会提示:
但是表里面始终没有数据进来,仔细分析日志就会发现,txt 会被 Lightning 默认的 filter 给过滤掉:
事实上,Lightning 提供了文件路由的特性,这也是 Lightning 能够导入 Aurora parquet 文件的原因,Aurora 的数据文件并不是我们熟知的库名.表名.csv|sql
这种格式,正是通过自定义解析文件名才实现了 Aurora 数据导入。参考文档上的一段配置信息:
文件路由通过mydumper.files
配置实现,它用正则定义了库名表名的解析规则。我们参考这个规则,在前面的lightning-task.yaml
中增加这样一段配置:
从 type 字段测试得出,Lightning 确实是不支持 txt 文件,但是这里通过正则解析巧妙的绕过了这个问题,把 txt 当做 csv 去处理。当强制给 type 设置为 txt 的时候,你会收到如下报错:
至此,我们实现了一个简单的 txt 文件导入。
Lightning 对复杂分隔符的处理
之所以选择用 txt 文件保存数据,就是因为它支持更多复杂的分隔符。一般来说,为了避免和真实数据冲突,我们会选用组合字符或者不可见字符来作为分隔符,比如^&^
、ESC
这种。
不可见字符是没办法直接写在配置文件中的,好在 Lightning 支持使用 Unicode 编码格式。 假设现在使用键盘上的ESC
作为分隔符,那就可以在配置文件中这样定义:
在toml
文件中,Unicode 字符需要使用 \u 来转义,001b 就是ESC
键对应的 Unicode 编码,并且这里字段值必须要用双引号包裹起来,单引号不行,需要注意。
Unicode 属于通用的字符编码规范,所有平台、系统、编程语言都对它有很好支持,建议在使用不常见字符时优先考虑使用 Unicode。
同样的,如果分隔符是多个字符,比如:
也能使用 Unicode 编码替换:
Lightning 对自定义文件名解析的处理
回到刚才新加的一段支持 txt 导入的配置:
可以发现这个配置是写死了库名、表名、以及文件名的,单个文件导入这样做没问题,如果有一大批 txt 需要导入,每个文件写一套配置肯定是不行,这时候需要用到它的正则解析特性。这个解析的核心就是,告诉 Lightning 如何提取需要导入的文件以及它对应的库名表名。
假设我现在有一批从其他库导出的 txt 文件,名称如下:
一般来说文件名都不会随便乱起一个,会带上自身的业务属性。比如上面这个例子第一个单词表示业务单元,中间的单词是业务表,最后的 f 表示这是个导出的文件。基于规则固定的情况下,我们就可以使用正则提取需要的信息,得到如下配置参数:
这样一来,只有符合 pattern 定义的文件才被 Lightning 处理,比如刚才的test.t.txt
就会被忽略掉。其次 schema 和 table 变得更加灵活,除了直接从正则参数提取,还能加入我们想要的 prefix,比如把文件都导入到以bak_
开头的表中:
有了这个特性,就算你的数据文件不是库名.表名.{index}.csv|txt
这种格式,也能通过配置参数解决了。
Lightning 对特殊格式的处理
上游的数据总是千奇百怪,往往无法预料会蹦出个什么格式,在数据导入的过程中有两点我觉得需要重点关注一下。
1、如何处理空值(null)
Lightning 定义了如下的空值解析规则(搬运自官网):
以上配置的含义是如果碰到aa,\N,11
这样的数据,那么中间字段在数据库里面会是 NULL。通常情况下我们会碰到这样的数据aa,,11
,那么就需要设置null = ''
。
如果不希望数据库里面存在 NULL 值,那么把not-null
设置为true
即可。
2、如何处理转义字符
Lightning 定义了如下的转义规则(搬运自官网):
假设恰好碰到这样的数据aa,\,11
,上面的配置会把第二个分隔符当做真实数据保留,实际只会导入 2 个字段,插入的值分别是aa
、,11
,使用的时候千万要注意。
如果要把\
当做真实数据写入第二个字段,那么把上述配置设置为false
即可。
大文件导入优化
Lightning 的最佳工作模式是处理大量的小文件,官网给出的建议值是单个数据文件不超过 256M,经过实测发现,默认情况下 Lightning 对大文件的处理确实不够理想,风险包括:
无法充分利用机器资源
导入速度极慢
程序易中断报错
进程假死无响应
不仅仅是 Lightning ,我觉得整个 TiDB 的使用精髓就是拆分拆分拆分,大而重的事情虽然 TiDB 能做,但不是它擅长的。类似于大事务 SQL 一样,这里我们需要把大文件做拆分。我使用过的有两种方式。
1、Lightning 严格模式
如果要导入的文件能够保证真实数据不包含换行符(\r\n),那么可以开启 Lightning 的严格模式来自动拆分大文件,达到加速目的。
相关参数为(务必仔细阅读参数说明):
2、手动切分文件
严格模式虽然好用,但是拆分逻辑在 Lightning 内部完成,我们无法知道具体拆分细节,如果出现数据问题就很难排查,手动拆分文件相对来说比较可控,也可以作为备选方案。
手动拆分的核心是使用 Linux 的split
命令,这里推荐一个基于split
封装的脚本,功能强大,为 Lightning 而生。
https://github.com/jansu-dev/TiChange_for_lightning
感谢作者的分享 @jansu-dev
TiChange 用起来最舒服的就是它能把拆分后的文件命名为 Lightning 需要的格式,这样就不用额外写正则去定义文件路由,使用方法可以参考 Github 文档,非常简单。
宝贵提示:如果不需要替换文件里的分隔符和界定符为 csv 标准格式,可以把源码中这部分的处理逻辑(多个 sed 操作)去掉,能够极大提高拆分速度。
我用一个 20G 的文件得到一组测试数据,供大家参考:
生产环境实践
近期上线的一个项目约有 100 个铺底数据文件,累计大小 12T+,单个文件最大 2.1T,采用手动拆分 + 分批导入的方案,6 台物理机同时干活,充分利用现有的机器资源。

最后累计在 1 天内完成数据导入,这里涉及到生产敏感数据不过多描述。
总结
毫无疑问,在往 TiDB 导入大数据量的时候首选一定是 Lightning ,它不仅支持官网明码标注的文件类型,还支持 txt 这样的彩蛋,好好研究一下 Lightning 是很有必要的。
另外,Lightning 也随着 TiDB 的版本升级在不断强大,建议优先使用高版本的 Lightning ,可以避免一部分已知的 bug,还能体验更好的性能。
虽然全篇都在以 txt 文件作为演示,但 csv 文件也同样适用前面描述的几种处理方式。
最后,希望本文能帮助到正在受大文件导入折磨的小伙伴们~
版权声明: 本文为 InfoQ 作者【TiDB 社区干货传送门】的原创文章。
原文链接:【http://xie.infoq.cn/article/179c47c875980e56f8fa25d0c】。文章转载请联系作者。
评论