背景
上一篇《记一次简单的Oracle离线数据迁移至TiDB过程》说到在使用Lightning导入csv文件到TiDB的时候发现了一个bug,是这样一个过程。
Oracle源库中表名都是大写,经过前文所述的方法导入到TiDB后表名也是保持全大写,数据同步过程非常顺利。
第二天我把整套操作流程教给一位新手朋友,他就挑了一张表用来做实验,结果死活都不行。各种分析和重试都没有效果,就在快要懵逼的时候想到了这个大小写问题,把csv拉出来一看是个全小写的文件名,我尝试着把表名改成大写再导入一次,这次终于成功了。
原来,是这位小伙子用sqluldr2导出表数据的时候把文件名写死了,而且是个小写。。。
那么,说好的TiDB表名不区分大小写呢,怎么用了Lightning就失效了?
Bug重现
上面说的还是有点抽象,我们通过如下的步骤重现一下。
这里我准备的TiDB测试版本是v5.2.2,和前面发现bug的版本一致,Lightning也使用配套的版本。我拿最新的master分支也能复现这个问题。
先创建一张测试表,表名全部用大写:
use test;
create table LIGHTNING_BUG (f1 varchar(50),f2 varchar(50),f3 varchar(50));
再准备一个待导入的csv文件,文件名是test.lightning_bug.csv
:
111|aaa|%%%
222|bbb|###
Lightning的完整配置文件:
[lightning]
level = "info"
file = "tidb-lightning.log"
index-concurrency = 2
table-concurrency = 5
io-concurrency = 5
[tikv-importer]
backend = "local"
sorted-kv-dir = "/tmp/tidb/lightning_dir"
[mydumper]
data-source-dir = "/tmp/tidb/data"
no-schema = true
filter = ['*.*']
[mydumper.csv]
# 字段分隔符,支持一个或多个字符,默认值为 ','。
separator = '|'
# 引用定界符,设置为空表示字符串未加引号。
delimiter = ''
# 行尾定界字符,支持一个或多个字符。设置为空(默认值)表示 "\n"(换行)和 "\r\n" (回车+换行),均表示行尾。
terminator = ""
# CSV 文件是否包含表头。
# 如果 header = true,将跳过首行。
header = false
# CSV 文件是否包含 NULL。
# 如果 not-null = true,CSV 所有列都不能解析为 NULL。
not-null = false
# 如果 not-null = false(即 CSV 可以包含 NULL),
# 为以下值的字段将会被解析为 NULL。
null = '\N'
# 是否对字段内“\“进行转义
backslash-escape = true
# 如果有行以分隔符结尾,删除尾部分隔符。
trim-last-separator = false
[tidb]
host = "x.x.x.x"
port = 4000
user = "root"
password = ""
status-port = 10080
pd-addr = "x.x.x.x:2379"
[checkpoint]
enable = false
[post-restore]
checksum = false
analyze = false
运行如下命令开始执行导入任务:
./tidb-lightning --config tidb-lightning.toml --check-requirements=false
报错信息:
日志里面全部是Info,除了没有正常输出tidb lightning exit
以外,看不到任何报错,一幅岁月静好的样子:
我认为这里的主要问题是,panic非常不友好,而且提示信息不够明确,虽然说了是空指针异常不过没什么参考价值,当时还被segmentation violation
误导了好久,一直怀疑是数据格式有问题。
我意识到这个bug应该不难,于是自己拉了一份TiDB源码开始定位问题。
Lightning的处理流程
Lightning的入口文件是br/cmd/tidb-lightning/main.go
,而它的核心实现都放在br/pkg/lightning
目录下。
我根据报错的堆栈信息倒推整个Lightning的导入流程,首先定位到restore.go
文件第1311行,我看到如下代码:
根据直觉,猜测tableInfo
是一个nil
值,以至于在取tableInfo.Name
的时候报出空指针异常。如果是这样的话,证明是表名不存在导致,但我记得表不存在的时候它的报错信息是这样:
(实际上在客户现场的时候慌的一批🤣),啃一啃源码也挺有意思的。