TiDB Lightning 错误处理功能

从 TiDB 5.4.0 开始,你可以配置 TiDB Lightning 以跳过诸如无效类型转换、唯一键冲突等错误,让导入任务持续进行,就如同出现错误的行数据不存在一样。你可以依据生成的报告,手动修复这些错误。该功能适用于以下场景:

  • 要导入的数据有少许错误
  • 手动定位错误比较困难
  • 如果遇到错误就重启 TiDB Lightning,代价太大

本文介绍 TiDB Lightning 错误处理功能涉及的错误种类、查询方法,并提供了一个示例。本文涉及的配置项如下:

  • lightning.max-error:类型错误的容忍阈值
  • conflict.strategyconflict.thresholdconflict.max-record-rows:数据冲突错误的相关配置
  • tikv-importer.duplicate-resolution(从 v8.0.0 开始已被废弃,并将在未来版本中被移除):物理导入模式下的冲突处理配置
  • lightning.task-info-schema-name:冲突数据存储的库名

相关配置项详情请参考 TiDB Lightning 任务配置

类型错误 (Type error)

你可以通过修改配置项 lightning.max-error 来增加数据类型相关的容错数量。如果设置为 N,那么 TiDB Lightning 允许数据源中出现 N 个类型错误,而且会跳过这些错误继续导入,一旦超过这个错误数就会退出。默认值为 0,表示不允许出现错误。

这些错误会被记录到数据库中。在导入完成后,你可以查看数据库中的数据,手动进行处理。请参见错误报告

  1. [lightning]
  2. max-error = 0

该配置对下列错误有效:

  • 无效值。例如:在 INT 列设置了 'Text'
  • 数字溢出。例如:在 TINYINT 列设置了 500
  • 字符串溢出。例如: 在 VARCHAR(5) 列中设置了'非常长的文字'
  • 零日期时间,如 '0000-00-00''2021-12-00'
  • 在 NOT NULL 列中设置了 NULL
  • 生成的列表达式求值失败
  • 列计数不匹配。行中数值的数量和列的数量不一致
  • 其他 SQL 错误

下列错误是致命错误,不能通过配置 lightning.max-error 跳过:

  • 原始 CSV、SQL 或者 Parquet 文件中的语法错误,例如未闭合的引号
  • I/O、网络、或系统权限错误

冲突错误 (Conflict error)

你可以通过修改配置项 conflict.threshold 来增加冲突错误相关的容错数量。如果设置为 N,那么 TiDB Lightning 允许数据源中出现 N 个冲突错误,而且会跳过这些错误继续导入,一旦超过这个错误数就会退出。在逻辑导入模式或者物理导入模式下,不同的场景会产生冲突错误,你可以参考对应导入模式的“冲突检测”文档。该配置项默认值为 10000,意味着能容忍 10000 个错误。

这些错误会被记录到数据库中。在导入完成后,你可以查看数据库中的数据,手动进行处理。请参见错误报告

错误报告

如果 TiDB Lightning 在运行过程中收集到报错的记录,则在退出时会同时在终端和日志中输出各个类型报错数量的统计信息。

  • 输出在终端的报错统计如下表所示:

    #ERROR TYPEERROR COUNTERROR DATA TABLE
    1Data Type1000lightning_task_info.type_error_v1
  • 输出在 TiDB Lightning 的 log 文件的结尾如下:

    1. [2022/03/13 05:33:57.736 +08:00] [WARN] [errormanager.go:459] ["Detect 1000 data type errors in total, please refer to table `lightning_task_info`.`type_error_v1` for more details"]

所有错误都会写入下游 TiDB 集群 lightning_task_info 数据库中的表中。在导入完成后,如果收集到报错的数据,你可以根据数据库中记录的内容,手动进行处理。

你可以使用 lightning.task-info-schema-name 配置更改数据库名称。

  1. [lightning]
  2. task-info-schema-name = 'lightning_task_info'

在此数据库中,TiDB Lightning 创建了 3 个表:

  1. CREATE TABLE type_error_v1 (
  2. task_id bigint NOT NULL,
  3. create_time datetime(6) NOT NULL DEFAULT now(6),
  4. table_name varchar(261) NOT NULL,
  5. path varchar(2048) NOT NULL,
  6. offset bigint NOT NULL,
  7. error text NOT NULL,
  8. row_data text NOT NULL
  9. );
  10. CREATE TABLE conflict_error_v1 (
  11. task_id bigint NOT NULL,
  12. create_time datetime(6) NOT NULL DEFAULT now(6),
  13. table_name varchar(261) NOT NULL,
  14. index_name varchar(128) NOT NULL,
  15. key_data text NOT NULL,
  16. row_data text NOT NULL,
  17. raw_key mediumblob NOT NULL,
  18. raw_value mediumblob NOT NULL,
  19. raw_handle mediumblob NOT NULL,
  20. raw_row mediumblob NOT NULL,
  21. KEY (task_id, table_name)
  22. );
  23. CREATE TABLE conflict_records (
  24. task_id bigint NOT NULL,
  25. create_time datetime(6) NOT NULL DEFAULT now(6),
  26. table_name varchar(261) NOT NULL,
  27. path varchar(2048) NOT NULL,
  28. offset bigint NOT NULL,
  29. error text NOT NULL,
  30. row_id bigint NOT NULL COMMENT 'the row id of the conflicted row',
  31. row_data text NOT NULL COMMENT 'the row data of the conflicted row',
  32. KEY (task_id, table_name)
  33. );

type_error_v1 记录由 lightning.max-error 配置项管理的所有类型错误 (Type error)。每个错误一行。

conflict_error_v2 记录物理导入模式的 conflict 配置组的冲突错误,每对冲突占两行。

conflict_records 记录逻辑导入模式和物理导入模式 conflict 配置组的冲突错误,每个错误占一行。

列名语法类型冲突说明
task_id生成此错误的 TiDB Lightning 任务 ID
create_time记录错误的时间
table_name包含错误的表的名称,格式为 db.tbl
path包含错误文件的路径
offset文件中发现错误的字节位置
error错误信息
context围绕错误的文本
index_name冲突中唯一键的名称。主键冲突为 ‘PRIMARY’
key_data导致错误的行的格式化键句柄。该内容仅供人参考,机器不可读
row_data导致错误的格式化行数据。该内容仅供人参考,机器不可读
raw_key冲突的 KV 对的键
raw_value冲突的 KV 对的值
raw_handle冲突行的行句柄
raw_row冲突行的编码值

可容忍错误 - 图1

注意

错误报告记录的是文件偏移量,不是行号或列号,因为行号或列号的获取效率很低。你可以使用下列命令在字节位实现快速跳转(以 183 为例):

  • shell:输出前面几行

    1. head -c 183 file.csv | tail
  • shell,输出后面几行

    1. tail -c +183 file.csv | head
  • vim::goto 183183go

示例

在该示例中,我们准备了一个包含一些已知错误的数据源。以下是处理这些错误的具体步骤:

  1. 准备数据库和表结构:

    1. mkdir example && cd example
    2. echo 'CREATE SCHEMA example;' > example-schema-create.sql
    3. echo 'CREATE TABLE t(a TINYINT PRIMARY KEY, b VARCHAR(12) NOT NULL UNIQUE);' > example.t-schema.sql
  2. 准备数据:

    1. cat <<EOF > example.t.1.sql
    2. INSERT INTO t (a, b) VALUES
    3. (0, NULL), -- 列不为空
    4. (1, 'one'),
    5. (2, 'two'),
    6. (40, 'forty'), -- 与下面的 `40` 冲突
    7. (54, 'fifty-four'), -- 与下面的 `'fifty-four'` 冲突
    8. (77, 'seventy-seven'), -- 字符串长度超过 12 个字符
    9. (600, 'six hundred'), -- 数字超出了 TINYINT 数据类型支持的范围
    10. (40, 'forty'), -- 与上面的 `40` 冲突
    11. (42, 'fifty-four'); -- 与上面的 `'fifty-four'` 冲突
    12. EOF
  3. 配置 TiDB Lightning,启用严格 SQL 模式,使用 Local 后端模式进行导入,通过替换解决重复项,并最多跳过 10 个错误:

    1. cat <<EOF > config.toml
    2. [lightning]
    3. max-error = 10
    4. [tikv-importer]
    5. backend = 'local'
    6. sorted-kv-dir = '/tmp/lightning-tmp/'
    7. [conflict]
    8. strategy = 'replace'
    9. [mydumper]
    10. data-source-dir = '.'
    11. [tidb]
    12. host = '127.0.0.1'
    13. port = 4000
    14. user = 'root'
    15. password = ''
    16. sql-mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE'
    17. EOF
  4. 运行 TiDB Lightning。因为已跳过所有错误,该命令执行完会成功退出:

    1. tiup tidb-lightning -c config.toml
  5. 验证导入的表仅包含两个正常行:

    1. $ mysql -u root -h 127.0.0.1 -P 4000 -e 'select * from example.t'
    2. +---+-----+
    3. | a | b |
    4. +---+-----+
    5. | 1 | one |
    6. | 2 | two |
    7. +---+-----+
  6. 检查 type_error_v1 表是否捕获了涉及类型转换的三行:

    1. $ mysql -u root -h 127.0.0.1 -P 4000 -e 'select * from lightning_task_info.type_error_v1;' -E
    2. *************************** 1. row ***************************
    3. task_id: 1635888701843303564
    4. create_time: 2021-11-02 21:31:42.620090
    5. table_name: `example`.`t`
    6. path: example.t.1.sql
    7. offset: 46
    8. error: failed to cast value as varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin for column `b` (#2): [table:1048]Column 'b' cannot be null
    9. row_data: (0,NULL)
    10. *************************** 2. row ***************************
    11. task_id: 1635888701843303564
    12. create_time: 2021-11-02 21:31:42.627496
    13. table_name: `example`.`t`
    14. path: example.t.1.sql
    15. offset: 183
    16. error: failed to cast value as varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin for column `b` (#2): [types:1406]Data Too Long, field len 12, data len 13
    17. row_data: (77,'seventy-seven')
    18. *************************** 3. row ***************************
    19. task_id: 1635888701843303564
    20. create_time: 2021-11-02 21:31:42.629929
    21. table_name: `example`.`t`
    22. path: example.t.1.sql
    23. offset: 253
    24. error: failed to cast value as tinyint(4) for column `a` (#1): [types:1690]constant 600 overflows tinyint
    25. row_data: (600,'six hundred')
  7. 检查 conflict_error_v1 表是否捕获了具有唯一键/主键冲突的四行:

    1. $ mysql -u root -h 127.0.0.1 -P 4000 -e 'select * from lightning_task_info.conflict_error_v1;' --binary-as-hex -E
    2. *************************** 1. row ***************************
    3. task_id: 1635888701843303564
    4. create_time: 2021-11-02 21:31:42.669601
    5. table_name: `example`.`t`
    6. index_name: PRIMARY
    7. key_data: 40
    8. row_data: (40, "forty")
    9. raw_key: 0x7480000000000000C15F728000000000000028
    10. raw_value: 0x800001000000020500666F727479
    11. raw_handle: 0x7480000000000000C15F728000000000000028
    12. raw_row: 0x800001000000020500666F727479
    13. *************************** 2. row ***************************
    14. task_id: 1635888701843303564
    15. create_time: 2021-11-02 21:31:42.674798
    16. table_name: `example`.`t`
    17. index_name: PRIMARY
    18. key_data: 40
    19. row_data: (40, "fourty")
    20. raw_key: 0x7480000000000000C15F728000000000000028
    21. raw_value: 0x800001000000020600666F75727479
    22. raw_handle: 0x7480000000000000C15F728000000000000028
    23. raw_row: 0x800001000000020600666F75727479
    24. *************************** 3. row ***************************
    25. task_id: 1635888701843303564
    26. create_time: 2021-11-02 21:31:42.680332
    27. table_name: `example`.`t`
    28. index_name: b
    29. key_data: 54
    30. row_data: (54, "fifty-four")
    31. raw_key: 0x7480000000000000C15F6980000000000000010166696674792D666FFF7572000000000000F9
    32. raw_value: 0x0000000000000036
    33. raw_handle: 0x7480000000000000C15F728000000000000036
    34. raw_row: 0x800001000000020A0066696674792D666F7572
    35. *************************** 4. row ***************************
    36. task_id: 1635888701843303564
    37. create_time: 2021-11-02 21:31:42.681073
    38. table_name: `example`.`t`
    39. index_name: b
    40. key_data: 42
    41. row_data: (42, "fifty-four")
    42. raw_key: 0x7480000000000000C15F6980000000000000010166696674792D666FFF7572000000000000F9
    43. raw_value: 0x000000000000002A
    44. raw_handle: 0x7480000000000000C15F72800000000000002A
    45. raw_row: 0x800001000000020A0066696674792D666F7572