MySQL与TiDB全量数据互导:Dumpling+Lightning方案详解
技术分享 | MySQL 和 TiDB 互相快速导入全量数据数据库迁移这件事最怕的不是数据量大而是导到一半发现方案选错了。最近在做一套跨数据库的数据平台改造需要在 MySQL 和 TiDB 之间来回搬全量数据两头都要导而且数据量都不小。一套是线上业务库单表过亿另一套是分析场景的 TiDB 集群要定期回灌数据供业务方查询。刚开始我也没当回事想着无非是 mysqldump 导出、source 导入结果第一次实操就被现实教育了几亿行的表用传统逻辑备份恢复的方式光导出就要跑十几个小时导入更是遥遥无期。后来把 Dumpling、TiDB Lightning、mydumper 这几个工具挨个试了一遍才真正摸清了两个方向上的最优姿势。这篇文章就把我实测下来最实用的方案、参数、坑和排查思路完整梳理一遍希望能帮你少走几个月的弯路。内容主要面向 DBA、运维、后端研发以及任何需要在 MySQL 和 TiDB 之间搬数据的同学新手也能照着操作老手可以重点关注后半部分的兼容性陷阱。1. 场景拆解与方案选型逻辑1.1 搞清楚你到底需要哪个方向很多人一上来就搜“MySQL 导入 TiDB”但实际需求没那么简单。在我这次改造里其实是两个方向都要做方向一MySQL 全量数据导入 TiDB常见于业务从 MySQL 迁移到 TiDB或者搭建 TiDB 分析集群后把在线库的历史数据同步过去。这个方向是要把 MySQL 当作数据源TiDB 当作目标端。方向二TiDB 全量数据导回 MySQL常见于 TiDB 和 MySQL 双跑期间需要回迁数据、分析结果落库、或者做环境重置时把 TiDB 中的清洗结果导回 MySQL 业务库。两个方向看起来很对称但实际操作路径完全不同。原因在于MySQL 的导入工具链是围绕 mysqldump、binlog 这种日志逻辑设计的而 TiDB 为了发挥分布式优势提供了自己的高速导入工具 TiDB Lightning。从 MySQL 进 TiDB最优路径是 Dumpling 导出 Lightning 导入从 TiDB 回 MySQL则要回归传统 SQL 逻辑备份用 Dumpling 导出成 SQL 文件再通过 MySQL 的 source 命令导入。如果不先确定方向就直接搜工具推荐很容易被错误的方案带偏。比如说你想从 TiDB 导数据回 MySQL结果照着“MySQL 导 TiDB”的教程装了 Lightning那就完全没有用武之地因为 Lightning 是目标端为 TiDB 的导入工具。1.2 工具选型的底层逻辑这块涉及的工具主要有四类我先给个直观对比后面再逐个讲实测参数和注意事项。工具适用方向导出/导入格式并发能力适合场景mysqldumpMySQL 导出SQL单线程或少量并发数据量小、结构简单mydumperMySQL 导出CSV/SQL多线程大表、需要拆分文件DumplingMySQL/TiDB 导出通用SQL/CSV多线程两边都推荐首选TiDB Lightning导入 TiDBSQL/CSV/Parquet极高SST/表级并发MySQL 数据入 TiDB 首选选择工具时核心考量点有三个并发模型、断点续传能力、格式兼容性。mysqldump 最大的问题是单线程导出大表时会非常吃亏而且一旦网络抖动中断就得从头再来。mydumper 是经典的 MySQL 并行备份工具并发粒度是表级别的对单张超大表支持不如按 chunk 切片的方案好。Dumpling 是 TiDB 官方推出的导出工具本身就是为分布式数据库场景设计的既可以连 MySQL 也可以连 TiDB 导出支持按表并发、按 chunk 切片还能自动控制输出文件大小。Lightning 则是把数据直接以 TiKV 原生格式写入底层存储能够绕过 SQL 层的解析、事务开销速度因此比普通导入高出一个数量级。再补一个核心原则导出工具负责把数据完整无损地拿出来导入工具负责以最高效的方式灌进去两者是独立的。所以你可以用 Dumpling 从 MySQL 导出再用 Lightning 导入 TiDB反过来也可以只用 Dumpling 从 TiDB 导出 SQL再交给 MySQL 的 source 命令。理解这个两层结构后任何排列组合都不容易搞乱。2. MySQL 全量数据导入 TiDB 的完整方案2.1 为什么不要直接用 mysqldump 导入 TiDB很多第一次接触 TiDB 的同学会下意识地执行mysqldump dbname | mysql -h tidb-server这种方式实测下来非常痛苦。TiDB 兼容 MySQL 协议所以这条命令语法上能跑通但性能完全发挥不出来。原因有以下几点mysqldump 默认生成的是单条大 INSERT 或逐条 INSERTTiDB 要通过 SQL 解析层执行每一行数据的写入事务开销巨大。mysqldump 的结构语句里常带有 ENGINEInnoDB、CHARSET 等 MySQL 特有的建表选项TiDB 虽然能解析但可能产生不合理的表属性推断。没有断点续传能力一个 10 亿行的表导入到一半网络闪断重启后所有进度归零。如果只是几百 MB 的小库用 mysqldump 管道导一下确实方便。但一旦数据量超过几个 GB或者单表行数过千万建议直接换成 Dumpling 导出 Lightning 导入的黄金组合。2.2 Dumpling 导出 MySQL 数据的具体操作Dumpling 是 TiDB 官方开源的导出工具用法很简单一条命令就能把 MySQL 中的数据导出到本地目录。我常用的命令如下dumpling \ -h 127.0.0.1 \ -P 3306 \ -u root \ -p your_password \ -B mydb \ -o /data/export/mydb \ -F 256MiB \ -t 8 \ --filetype sql这里有必要逐个说清参数含义因为每个参数都对应一个性能拐点-B mydb指定导出库名。如果你想导出多个库可以省略-B直接导出所有库但建议明确指定避免把 mysql 系统库也搬过去。-F 256MiB单个文件的最大大小。Dumpling 会自动把大表的数据拆分成多个 256MB 的文件这样做的好处是 Lightning 导入时可以按文件粒度调度并行度更高断点续传的粒度也更细。-t 8导出线程数。这个值不是越大越好我实测在 MySQL 所在机器为 8 核时线程数设为 8 能达到吞吐峰值盲目调到 32 反而会让 MySQL 端产生大量临时内存占用导出性能反而下降。--filetype sql输出 SQL 格式文件每个文件包含建表语句和 INSERT 语句。如果你是用 Lightning 导入 TiDBSQL 格式就行如果你要导成 CSV 给大数据系统用可以改成--filetype csv。执行完导出后记得检查输出目录里有没有metadata文件这个文件记录了导出时的一致性快照位点是后期增量同步的重要依据。同时看一下*-schema.sql文件是否完整这些文件定义了表结构导入时 Lightning 会先执行它们。2.3 TiDB Lightning 导入的核心配置TiDB Lightning 的配置是一个 TOML 文件我最常用的一套配置如下[lightning] level info file tidb-lightning.log check-requirements true [mydumper] source-dir /data/export/mydb [tidb] host 127.0.0.1 port 4000 user root password status-port 8280 pd-addr 127.0.0.1:2379 max-allowed-packet 67108864 [tikv-importer] backend local sorted-kv-dir /data/sorted-kv-dir [checkpoint] enable true keep-after-success false有三个配置项需要特别说明backend local是最关键的一项。它表示采用本地物理导入模式Lightning 会把数据解析成键值对排序后直接写入 TiKV 节点。这个模式绕过了 TiDB SQL 层的写入路径所以速度极快。如果改成backend tidb就退回成了 SQL 导入性能会下降好几个档次。sorted-kv-dir是本地排序目录建议放在空间充足的磁盘上且目录所在磁盘剩余空间要大于待导入数据总量。如果磁盘不够导入会直接报错。checkpoint.enable true开启断点续传这样即使中途崩溃重启 Lightning 后也会从断点位置继续导入而不是从头再来。我实际测试过一个 5 亿行的表在导入 60% 时中断重启后只用了不到 15 分钟就补完了剩余部分。启动导入的命令是tiup tidb-lightning -config tidb-lightning.toml导入过程会输出类似progress: 100%的日志全部完成后日志末尾会显示tidb lightning exit字样表示导入成功。2.4 Python 脚本校验数据一致性导入完成不等于万事大吉我每次都会做一次表级行数校验。最简单的方式是分别连 MySQL 和 TiDB 执行SELECT COUNT(*)但大表 count 开销很高而且两个库同时跑对线上业务有影响。我的做法是抽查关键大表并配合CHECKSUM TABLE做行级哈希校验。import pymysql def get_checksum(conn, table): with conn.cursor() as cur: cur.execute(fCHECKSUM TABLE {table}) return cur.fetchone()[1] mysql_conn pymysql.connect(host127.0.0.1, port3306, userroot, password, databasemydb) tidb_conn pymysql.connect(host127.0.0.1, port4000, userroot, password, databasemydb) for table in [user, order, product]: mysql_sum get_checksum(mysql_conn, table) tidb_sum get_checksum(tidb_conn, table) print(f{table}: mysql{mysql_sum}, tidb{tidb_sum}, OK if mysql_sum tidb_sum else MISMATCH)这里要提醒一下CHECKSUM TABLE 在 MySQL 和 TiDB 中的计算算法是基于存储引擎内部数据的两边数值大概率不一致所以不能直接比较字符串。我上面的脚本只是个框架真正的做法要么按主键分批抽样比对要么对核心业务表逐行比对这个后面在问题排查章节再展开讲。3. TiDB 全量数据导回 MySQL 的完整方案3.1 导出 SQL 格式文件的关键设置从 TiDB 导回 MySQL 的场景没有 Lightning 这种神器可用目标端是标准 MySQL所以只能走“逻辑备份导出 SQL 导入”的路线。Dumpling 依然是导出端的最佳选择但参数和前面略有不同。dumpling \ -h 127.0.0.1 \ -P 4000 \ -u root \ -p \ -B mydb \ -o /data/export/from_tidb \ -F 128MiB \ -t 16 \ --filetype sql \ --output-filename-template {{.Schema}}.{{.Table}}.{{.Index}}.sql区别主要在-t 16和--output-filename-template这两项。TiDB 作为分布式数据库导出时的并发能力比 MySQL 强很多线程数可以设置到 16 甚至 32亲测 16 以上吞吐提升就不明显了。文件名模板建议带上库名和表名这样后续导入 MySQL 时按照文件顺序执行不会因为文件切割而导致同一张表的数据分散后难以维护。这里需要注意一个容易被忽略的点Dumpling 从 TiDB 导出 SQL 时默认会把每行数据放在单独一条 INSERT 语句中。这样做内存占用低但导入 MySQL 时会慢很多。所以如果表数据量特别大可以在导出时增加--sql-insert-batch-size参数比如设为 1000让每条 INSERT 包含多行数据导入速度能提升好几倍。3.2 MySQL 端分批导入与参数调优导入 TiDB 导出的 SQL 文件到 MySQL本质上还是 mysql 命令配合 source。但直接执行下面这条命令往往会让人等得怀疑人生mysql -h 127.0.0.1 -P 3306 -u root -p mydb /data/export/from_tidb/mydb.user.sql原因很简单没有合理利用事务和批量写入特性。导入前需要先调整 MySQL 会话级参数SET FOREIGN_KEY_CHECKS 0; SET UNIQUE_CHECKS 0; SET AUTOCOMMIT 0;关闭外键检查可以避免外键约束带来的额外查询开销关闭唯一性检查能让 InnoDB 在插入时跳过一些辅助索引的重复校验关闭自动提交后多张表的 INSERT 可以在一个事务内完成磁盘刷新次数大幅减少。导入完成后记得再执行SET FOREIGN_KEY_CHECKS 1; SET UNIQUE_CHECKS 1; COMMIT;把参数恢复原样。但这里有个隐含风险如果关闭 AUTOCOMMIT 后中途导入失败事务回滚会让所有已插入的数据全部作废启动时非常痛苦。所以我的策略是把多个 SQL 文件按表分组每个表单独执行一次mysql命令并配合-e source ...的方式这样每个表独立成一个事务回滚范围可控。mysql -h 127.0.0.1 -P 3306 -u root -p mydb -e SET FOREIGN_KEY_CHECKS0; SET UNIQUE_CHECKS0; SET AUTOCOMMIT0; source /data/export/from_tidb/mydb.orders.sql; COMMIT;另外如果你的 SQL 文件特别大单个文件超过 2GB建议先用split按大小拆分一次或者让 Dumpling 导出时就生成更小的文件。MySQL 客户端处理超大 SQL 文件时容易出现内存暴涨或连接超时的情况这是我踩过不少次的坑。3.3 字符集和时区问题处理TiDB 和 MySQL 的默认字符集在常见部署下大多是 utf8mb4但如果你在 TiDB 侧设置了类似utf8mb4_bin的排序规则而 MySQL 侧的表是utf8mb4_general_ci导入时会遇到字符集转换带来的索引失效甚至报错。我在实际操作中会在 Dumpling 导出前确认两侧排序规则SELECT default_character_set_name, default_collation_name FROM information_schema.SCHEMATA WHERE schema_name mydb;如果发现不一致最简单的办法是在 MySQL 导入前预建表手动指定与 MySQL 兼容的 charset 和 collation然后让 Dumpling 导出时只用--no-schema跳过表结构避免它把 TiDB 的表结构定义直接搬过来引发兼容性问题。时区问题也值得一说。TiDB 默认时区可能和 MySQL 不同如果业务数据里有TIMESTAMP类型字段导入到 MySQL 后看到的时间可能偏移 8 小时。我处理方案是统一在导出和导入连接字符串里显式指定时区dumpling -h ... --tz Asia/Shanghai mysql -h ... --default-character-setutf8mb4确保两端都是同一个时区后面校验才会少很多麻烦。4. 兼容性差异与导入失败排查实录4.1 表结构兼容性差异盘点我在迁移过程中发现真正耗时间的不是导入动作本身而是表结构不兼容导致的反复报错。这里把最常见的几个差异点列一下希望你在导出前就做好筛查自增主键TiDB 支持 AUTO_INCREMENT但它的自增是全局单调分配MySQL 端如果表结构定义里有AUTO_INCREMENTxxx后缀导入时如果该值小于已有数据量会产生重复主键报错。建议导入前检查是否应在目标端去掉这个后缀。索引长度限制TiDB 对索引前缀长度的限制和 MySQL 不同如果 MySQL 端表里有VARCHAR(255)且使用 UTF8MB4 作为索引前缀可能在 TiDB 中会报“Specified key was too long”。反之从 TiDB 导回 MySQL 时也遇到过 MySQL 旧版本不支持过长的索引键。sql_mode差异TiDB 的 sql_mode 默认包含ONLY_FULL_GROUP_BY等项和 MySQL 的设置可能不一样导致导出的 SQL 在 MySQL 里执行触发严格模式的约束错误。稳妥的做法是两边都设成宽松模式或者导入前先SET sql_mode 。外键引用TiDB 虽然支持外键语法但很多业务在迁移到 TiDB 时都会去掉外键。如果你在 TiDB 侧有数据但目标 MySQL 表有外键约束导入时可能会出现违反外键的报错。这个问题最隐蔽因为导出时不一定报错导入时才炸。4.2 导入中途报错如何快速定位导入报错是常态关键是别慌。我通常按以下顺序快速定位先看错误码和错误信息。如果是ERROR 1062 (23000): Duplicate entry说明有重复主键或唯一键冲突优先排查是否是导出时的一致性快照没有做对导致重复导入了同一批数据。如果是ERROR 1366 (HY000): Incorrect string value说明字符集配置不对按前面 3.3 小节的方式处理即可。再查 Lightning 的日志文件。TiDB Lightning 对每个数据文件都会记录处理状态日志里如果出现write to tikv failed这类错误通常是目标 TiKV 集群的磁盘空间或负载问题。我遇到过一次 TiKV 节点磁盘写满Lightning 一直重试写入但始终失败看监控才发现是磁盘剩 0 字节。最后不要忘了检查目标端 MySQL 的max_allowed_packet参数。Dumpling 导出的大批量 INSERT 单条语句可能很大MySQL 默认的 4MB 包大小很容易报Packet too large错误。导入前先把 session 级max_allowed_packet调到 64MB 以上SET GLOBAL max_allowed_packet 67108864;这个坑我遇到多次每次排查到最后才发现是配置问题而不是数据问题。4.3 大表导入的性能瓶颈清单如果你导入速度始终上不去对照下面这张清单逐项排查基本能定位问题现象可能原因解决办法导出速度远低于磁盘 IO 上限Dumpling 线程数过低或 MySQL 端参数限制调大-t检查 MySQL 的innodb_buffer_pool_sizeLightning 导入时进度条卡在某个百分比目标 TiKV 热点、磁盘空间不足检查 TiKV 监控扩容磁盘或关掉集群上的其他业务负载MySQL source 导入时 CPU 使用率低但耗时很长自动提交开启频繁刷盘导入前执行SET AUTOCOMMIT0并手动提交导入时内存持续上涨直至 OOM单条 INSERT 包含行数过多调低 Dumpling 的--sql-insert-batch-size或调小-F文件大小导入完成后查询结果数量不对字符集或时区导致部分数据写入异常抽查特定表按主键范围分段计数比对5. 实操心得与后续扩展建议5.1 校验数据完整性的几种手段导入完成后的数据校验是整个迁移流程中最容易被忽视的一环。只检查行数不够因为行数相同不代表数据内容一致。我常用的几种校验手段按成本从低到高排列第一种是抽样抽查对每张表随机取 1000 条主键分别查两个库的单行数据逐个字段比对。这种方式最轻量适合验证迁移流程本身没有问题。第二种是分桶求和校验对数值型字段做 SUM对字符型字段做 CRC32 求和两个库分别算后比对。虽然不如精确逐行对比严谨但能捕捉到绝大多数写入错误。第三种才是精确校验用 DTS 工具或自己写脚本按主键分批拉取数据做全量对比成本最高但最可靠。对于核心业务表订单、用户资产等我建议无论成本多高都要做一次全量精确校验。我自己的习惯是全量校验只做一次核心表其他表用抽样加深层校验组合的方式效果已经很满意。5.2 从全量到增量迁移后的持续同步思路全量导入只是第一步很多业务场景需要导入后继续保持 MySQL 和 TiDB 的数据一致这时候全量导入的产物—— Dumpling 导出目录下的metadata文件——就成了增量同步的起点。MySQL 侧metadata 文件里记录的 binlog 位点可以用于后续增量同步工具的初始化TiDB 侧metadata 文件里记录了 TSO 快照时间戳可以作为增量同步的起点。如果你用的是官方同步工具只需要在配置里指定这个位点即可。这个衔接过程比我预想的要顺畅不少但也提醒一点metadata 文件非常关键千万别在迁移完成后当作临时文件清掉了。5.3 我对这套方案的使用体会我前后折腾了两周最后沉淀下来的方案就是MySQL 到 TiDB 用 Dumpling LightningTiDB 回 MySQL 用 Dumpling 导出 SQL 再分批 source。这套组合看起来传统但实测在 TB 级数据量下稳定性和效率都让我满意。个人最有价值的一个经验是任何你觉得“应该没问题”的环节都要提前在测试环境用小数据量完整走一遍特别是注意时区、字符集这类细节。因为在数据量不大的时候这些问题可能都不会暴露但一旦上了全量一个时区偏移就能让你排查好几个小时。最后再分享一个小技巧导完数据后别急着删导出文件至少保留 3 天这段时间足够你发现数据问题并快速重新导入而不必从头再导一次。