文章

MySQL 数据迁移(四)数据文件物理迁移实战与评测

逻辑迁移再怎么多线程,都绕不过 SQL 解析和 InnoDB 事务开销。物理迁移直接拷贝数据文件,让 MySQL'换壳不换魂'。本篇实战 XtraBackup 的 xbstream + qpress 流式压缩备份、prepare 恢复全流程,以及可传输表空间单表迁移方案,用同样的 2000 万行压测数据回答:物理迁移到底快多少,代价是什么。

MySQL 数据迁移(四)数据文件物理迁移实战与评测

文章信息

  • 原文链接:https://jiayq.blog.csdn.net/article/details/166594750
  • 发布时间:2026-09-24 15:36:46
  • 标签:#mysql, #数据库, #mysqldump, #mydumper, #myloader, #DTS

MySQL 数据迁移(四)数据文件物理迁移实战与评测

摘要

逻辑迁移再怎么多线程,都绕不过 SQL 解析和 InnoDB 事务开销。物理迁移直接拷贝数据文件,让 MySQL”换壳不换魂”。本篇实战 XtraBackup 的 xbstream + qpress 流式压缩备份、prepare 恢复全流程,以及可传输表空间单表迁移方案,用同样的 2000 万行压测数据回答:物理迁移到底快多少,代价是什么。

1. 建议先看

2. 物理迁移的原理:绕过 SQL 层

2.1 逻辑迁移的天花板

前两篇的方案无论怎么并行,每行数据都要走一遍完整流程:

1
SQL 文本 → SQL 解析器 → 执行器 → 事务(binhlog+redo+fsync) → 数据页

物理迁移直接跳过前面所有环节,拷贝的就是 InnoDB 的数据页文件(.ibd/.ibdata)——目标端拿到的是”已经写好的数据页”,恢复本质是文件搬运 + 少量日志回放。

2.2 核心难题:数据文件在”动”,怎么拷才一致

直接 cp 数据目录是不行的:拷贝期间业务还在写,拷出来的是”不同时刻的页”混在一起的废数据。XtraBackup 的解法:

  1. 拷贝数据文件的同时,持续追踪 redo log(记录拷贝期间的新写入)
  2. 拷贝完成后,拿到一个一致性位点(执行 LOCK INSTANCE FOR BACKUP
  3. prepare 阶段:用记录下来的 redo 重放,把”拷贝期间发生的写入”补进拷贝的文件里,最终得到”位点时刻的一致性快照”

一句话:数据文件抄旧一点没关系,redo 日志会把差异补齐——这正是 MySQL 崩溃恢复(crash recovery)的原理复用。

2.3 xbstream 与 qpress:流式打包与压缩

  • xbstream:XtraBackup 的流式归档格式,把备份产物(成百上千个文件)打包成单个流输出到 stdout,天然适合管道传输(| ssh、上传对象存储)
  • qpress:QuickLZ 算法压缩器,XtraBackup 生态的标配(历史上配合 --compress 内置压缩;XtraBackup 8.0 移除了内置压缩,改为外部工具压缩 xbstream 流)
1
2
3
4
5
6
7
# 导出:xbstream 流 → qpress 压缩 → 单个文件
xtrabackup --backup --stream=xbstream ... | qpress ... > backup.xbstream.qp

# 导入:qpress 解压 → xbstream 提取 → prepare
qpress -d backup.xbstream.qp ...
xbstream -x < backup.xbstream -C restore_dir
xtrabackup --prepare --target-dir=restore_dir

2.4 可传输表空间:单表级别的物理迁移

XtraBackup 迁移的是整个实例。如果只想迁一张表,用”可传输表空间”(Transportable Tablespace):

1
2
3
4
5
6
7
8
-- 源端:锁表刷盘,生成 .cfg 元数据文件
FLUSH TABLES sbtest1 FOR EXPORT;
-- 拷走 sbtest1.ibd + sbtest1.cfg 后解锁
UNLOCK TABLES;

-- 目标端:建同结构空表 → 丢弃自己的表空间 → 放入源端文件 → 导入
ALTER TABLE sbtest1 DISCARD TABLESPACE;
ALTER TABLE sbtest1 IMPORT TABLESPACE;

.cfg 文件记录了表结构指纹(列类型、顺序、表空间 ID),IMPORT TABLESPACE 时做严格校验——所以要求两端表结构完全一致(同版本建出的同 DDL)。

实测(sbtest1 表,100 万行 / 264MB 表空间文件):从源表 FLUSH TABLES ... FOR EXPORT 到目标端 IMPORT TABLESPACE 完成并校验(COUNT = 100 万),全程秒级完成——同样的表走 myloader 逻辑导入需要 8~10 秒。单表物理迁移的”导入”本质是让 InnoDB 直接认领一个已经写好的数据文件,264MB 的文件拷贝几乎就是全部成本。

两个实测踩坑(都是真实翻车现场):

  1. .ibd 文件拷入目标端后必须把属主改成 mysql(docker 部署时是 uid 999),否则 IMPORT TABLESPACE 直接失败且报错信息不明确(mysqld 打不开文件)
  2. FLUSH TABLES ... FOR EXPORT 会加表级读锁,锁期间这张表的写入全部阻塞——生产上对在线大表做这个操作要评估锁窗口

3. 实战:XtraBackup 全量迁移

3.1 备份

1
2
3
4
5
6
7
8
9
10
11
12
13
# 流式备份 + qpress 压缩(实测环境:Docker 部署 MySQL,xtrabackup 以容器方式运行)
docker run --rm --user=0 --network mysql-migrate-net \
  -v /data/mysql-migrate/single/data:/var/lib/mysql:ro \
  -v /backup:/backup \
  percona/percona-xtrabackup:8.0.35 \
  xtrabackup --backup \
    --host=mysql-single --port=3306 \
    --user=root --password=*** \
    --stream=xbstream --parallel=4 \
  > backup.xbstream

# qpress 压缩(跨机传输前打包)
qpress -c backup.xbstream backup.xbstream.qp

参数说明:

参数作用
--stream=xbstream备份产物打包成流输出到 stdout,不落本地多文件
--parallel=4数据文件拷贝并行度(多个文件同时拷)
--host/--user连接实例拿位点、执行备份锁(注意:不连实例的文件级拷贝是错的
--user=0(docker)容器内以 root 运行,否则无权读数据目录

实测耗时与产物(2000 万行 / 20 张表,压测伴随,单节点组):

阶段耗时说明
xtrabackup 流式备份150s产物 xbstream 5298MB
qpress 压缩39s3162MB(压缩比 0.59)
备份期间源库压测TPS 396 → 261(-34%物理备份的 IO 冲击显著大于逻辑导出

两个值得注意的数字:

  1. 物理产物(5298MB)比逻辑 SQL 裸文件(3810MB)还大:XtraBackup 拷贝的是 InnoDB 数据文件本身,包含空闲页、碎片页、undo 段、redo——”文件的物理体积”大于”数据的逻辑体积”。压缩后(3162MB)才反超 gzip 压缩的 SQL(1841MB),逻辑文本的压缩率天生好于二进制页文件
  2. 备份期间源库 TPS 掉了 34%:对比 mysqldump(+12%)和 mydumper(+4%)——物理备份一边顺序读全部数据文件、一边持续追踪 redo,IO 通道占得更满。这是”备份期间业务退化”最明显的一种

3.2 恢复:解压 → 提取 → prepare → 挂载

1
2
3
4
5
6
7
8
9
10
11
12
# 1. qpress 解压(注意:qpress -d 的目标参数是目录)
qpress -d backup.xbstream.qp /backup/

# 2. xbstream 提取成文件树
mkdir -p restore_dir
xbstream -x < backup.xbstream -C restore_dir

# 3. prepare:重放 redo,把备份推到一致性状态
xtrabackup --prepare --target-dir=restore_dir

# 4. 停目标实例 → 清空数据目录 → 拷入 → 改权限 → 启动
#    (物理恢复是整实例替换,目标端必须是空实例!)

实测耗时分解(单节点组,同上):

恢复阶段耗时说明
qpress 解压 + xbstream 提取35s5298MB 文件树还原
xtrabackup –prepare103s重放 redo,推到一致性位点
拷入 target + 重启就绪~42s文件级 cp + 实例启动
恢复总计180s 
全流程(备份+压缩+恢复)369s 
校验20/20 表行数一致物理恢复数据完整

3.3 验证与位点

恢复完成后,位点信息按 GTID 事务集合确认和衔接,三步闭环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. 备份产物里自带位点文件(file + pos + GTID 三种表达)
$ cat restore_dir/xtrabackup_binlog_info
mysql-bin.000007    1234567    3E11FA47-27CA-11EE-...:1-5000

# 2. 恢复后的实例自带完整的事务集合(与源库快照时刻一致)
mysql> SELECT @@GLOBAL.gtid_executed;
3E11FA47-27CA-11EE-...:1-5000

# 3. 增量追赶:GTID 复制直接接上源库,无需任何位点换算
mysql> CHANGE REPLICATION SOURCE TO
    ->   SOURCE_HOST='***', SOURCE_PORT=3306,
    ->   SOURCE_USER='repl', SOURCE_PASSWORD=***,
    ->   SOURCE_AUTO_POSITION=1;
mysql> START REPLICA;

一份数据三种位点表达:binlog 文件名、字节偏移、GTID 事务集合——第三列才是最有价值的。物理恢复出的实例自带完整的 gtid_executed(备份时刻源库已执行的事务集合,xtrabackup_binlog_info 的第三列),这是三个工具里位点信息最完整的一个:增量追赶直接 SOURCE_AUTO_POSITION=1,源库根据事务集合自动协商从哪里发,集合里已有的事务幂等跳过——不依赖 file+pos 那种脆弱的字节偏移(第二篇 2.4 的结论),也不需要像逻辑导出那样单独记录位点,恢复即得到。这也是物理迁移在”全量 + 增量”两阶段方案里天然好接的原因:恢复完的目标库在 GTID 维度上就是源库快照时刻的精确镜像。

4. 实验评测

4.1 三套架构结果总表

架构组备份源备份耗时恢复耗时(含prepare)全流程校验
单节点single 本机150s / 5298MB180s369s20/20 表行数一致
主从master 主库172s / 5291MB177s388s20/20 表行数一致
主从(补充组)slave 纯备库156s / 5169MB179s375s20/20 表行数一致
读写分离ro 只读节点169s / 5298MB169s378s20/20 表行数一致

补充组(压测打 master、xtrabackup 从 slave 纯备库备份)的实测:master 压测 TPS 414.08 → 270.48(-35%)、P95 28.16 → 31.37ms;slave 自身复制延迟最高 53 秒;备份 156s、恢复 179s(extract 36s + prepare 106s + 拷入启动 37s)。

三组附加数据横向对比,结论非常清晰:

备份打在谁身上主库压测 TPS 变化副本自身复制延迟
master 主库-38%从库 28s
slave 纯备库-35%slave 53s(最高)
ro 只读节点-32%ro 46s
  • 主从组:备份期间从库复制延迟最高 28 秒(均值 5.5s)——xtrabackup 占满 master 的 IO 通道后,binlog 下发和从库回放都被挤压。对比第三篇 mydumper 备份 master 时从库延迟仅 2 秒,物理备份对复制链路的冲击是逻辑导出的十倍量级
  • 读写分离组:备份打 ro、压测打 rw,ro 自身复制延迟最高冲到 46 秒——ro 一边被备份占满 IO 一边回放主库 binlog,双重压力下延迟比”主库备份+从库旁观”场景(28s)还要高。而 rw 的压测 TPS 也掉了 32%:ro 的备份 IO 和 rw 共享同一台宿主机的磁盘带宽
  • 纯备 slave 组(补充):延迟冲到 53 秒,是三组最高——备份 IO 与复制回放同时在 slave 上抢资源;主库 TPS 降 35%,与另外两组处于同一量级(-32%~-38%),说明物理备份对主库的影响主要经宿主机共享资源传导,而不是”备份打在哪个节点”决定的
  • 结论:物理备份的”从哪个节点做”更加重要,因为它对备份节点和其复制链路的冲击都是三个工具里最重的。生产上物理备份节点要选”不接业务读流量、且允许分钟级复制延迟”的副本——主从架构里不接读流量的纯备 slave 正好符合(第二篇 4.4 决策树):slave 的分钟级延迟上升不影响任何业务读,代价是延迟峰值更高(53s),只要它不服务读流量就完全无害

恢复侧三个阶段分解(主从组):qpress 解压 + xbstream 提取 37s、prepare 102s、拷入并启动目标实例 38s。prepare 占恢复的一半时间——它在重放备份期间积压的 redo,把”拷贝时的旧文件”补齐到”位点时刻的一致性状态”。

4.2 备份期间源库冲击:物理 vs 逻辑

同数据集、同压测方法,三种工具备份/导出期间的源库表现(各组内对比):

工具基线 TPS备份/导出期间 TPS组内变化P95 变化
mysqldump(第二篇)257~261269~288+4%~+12%持平或更好
mydumper 8线程(第三篇)326~371324~342-13%~+4%+24%(rwro组)
xtrabackup(本篇)396261-34%+15%

三条结论:

  1. 冲击强度:物理备份 > 多线程逻辑导出 > 单线程逻辑导出。直觉上”物理拷贝只是读文件”应该最轻,实测恰恰相反——顺序读满整个数据目录 + 持续追踪 redo 的双重 IO,比走 SQL 层的 SELECT 流式读更占磁盘带宽
  2. 前两篇的”TPS 不降反升”是数据集(4.2GB)≈ buffer pool(4G)的边界特例;xtrabackup 在这种特例下依然让 TPS 掉 34%,足见其 IO 占用强度
  3. 生产启发:物理备份更要挑低峰窗口。云厂商的物理备份策略普遍放在凌晨,原因就在这

4.3 三方案总对决

同数据集(2000 万行 / 4.2GB)全流程对比:

维度mysqldumpmydumper/myloaderxtrabackup
全流程耗时632s200s369s
备份/导出60s28s150s
恢复/导入529s172s180s
传输产物大小1841MB(gzip)1904MB(自带压缩)3162MB(qpress)
备份期间源库 TPS+12%+4%-34%
恢复侧确定性SQL 逐行执行SQL 逐行执行(并行)文件级,与行数无关
目标端要求库存在即可库存在即可空实例整库替换
版本兼容最好严苛

4.2GB 规模下的冠军是 mydumper/myloader(200s),物理方案(369s)反而居中。为什么”最快的物理迁移”没赢?

物理备份的耗时结构:拷贝 5.3GB 文件(150s)+ prepare 回放(103s)+ 文件级恢复(77s)——全部是”与数据体积成正比”的 IO 操作,与行数无关。而逻辑导入是”与行数成正比”的解析+执行操作。数据量小(行数少、体积小)时,逻辑导入的行级成本被多线程摊薄,物理方案的固定 IO 开销反而更重。

交叉点在哪? 逻辑导入的行成本是刚性的(每行都要过解析器),数据量翻倍导入时间近似翻倍;物理备份的时间近似只随数据体积线性增长。实测 myloader 导入速度约 116,000 行/s,在单机实验规格下,行数到亿级时物理方案就会开始反超;生产高配机器(更多核、NVMe 阵列)会把交叉点大幅推后,但趋势不变:数据量越大,物理优势越大——这也是生产经验把物理迁移定位在 10TB+ 超大规模场景的原因(第 6 节)。

4.4 高写入负载下的 redo 追赶陷阱

实测中发现一个真实问题:源库高速写入时(sysbench 造数据阶段,redo 以 30MB/s+ 的速度轮转),xtrabackup 报错:

1
2
[Xtrabackup] could not find redo log file with LSN 5269084160
[Xtrabackup] read_logfile() failed.

原因:MySQL 8.0 的 redo log 是环形复用的(默认容量 100MB),写入太快时,xtrabackup 还没读到的 redo 文件已经被覆盖轮转,LSN 追丢。解决:

  • 调大 innodb_redo_log_capacity(如 1G),给备份留出追赶余量
  • 或避开写入高峰做备份
  • XtraBackup 8.0.30+ 会提示这个错误并建议扩容 redo

这个问题在逻辑导出里不存在(SELECT 走 MVCC 快照,和 redo 无关),是物理备份特有的坑。

5. 会遇到的问题与解决方案

5.1 版本与参数强约束

物理文件迁移的兼容性约束比逻辑迁移严苛得多:

约束项要求
MySQL 大版本8.0 的物理备份不能恢复给 5.7,反之亦然
XtraBackup 版本与 MySQL 版本配套(8.0.x 备 8.0.y,建议 y 不超前太多)
innodb_page_size两端必须一致
lower_case_table_names两端必须一致
目标端状态必须空实例(整实例替换)

逻辑迁移(第二、三篇)几乎没有这些约束——这就是物理迁移”快”的代价。

5.2 目标端必须”空实例”

逻辑导入是往库里”加数据”,物理恢复是”换整个身体”。目标实例上已有的账号、参数、其他库,全部会被备份内容覆盖。生产操作前想清楚:目标端是新机器还是老机器复用。

5.3 prepare 不是可选项

跳过 prepare 直接启动恢复出来的数据目录 = 直接启动一个”崩溃状态”的 MySQL,轻则报错起不来,重则数据损坏。--prepare 就是让备份完成一次”受控的崩溃恢复”。

5.4 权限与路径

实测踩坑:容器化部署时,xtrabackup 找 binlog 文件用的是实例视角的路径(/var/lib/mysql),宿主机直接跑 xtrabackup 时路径对不上(报 File '/var/lib/mysql/mysql-bin.000007' not found)。解法:xtrabackup 也容器化运行,挂载相同路径。

5.5 可传输表空间的限制

  • 表结构必须严格一致(.cfg 校验)
  • 目标端要有同 DDL 的空表
  • 不支持分区表的部分场景、外键需要 foreign_key_checks=0
  • 8.0 之前跨版本传输受限(.cfg 格式不兼容)

6. 适用数据量级结论

生产经验值:XtraBackup 物理迁移的适用范围在 10TB 以上(超大数据量场景),结合前三篇形成完整的选型地图:

数据量级(生产经验)推荐方案核心理由
300GB 以内mysqldump(调参)最省心,窗口能等
300GB ~ 15TBmydumper/myloader逻辑迁移主力区间,多线程摊薄行级成本
10TB 以上XtraBackup 物理迁移恢复耗时只与数据体积相关、与行数无关,数据量越大优势越大
单表级迁移可传输表空间单表 264MB 实测秒级导入,跳过全部 SQL 开销

注:10TB ~ 15TB 之间是一段重叠区——这个区间 mydumper 和 XtraBackup 都能用,按迁移窗口要求和运维熟悉度选即可。

物理迁移的三条铁律:版本/页大小/参数必须匹配、目标端必须空实例(停机替换)、prepare 一步不能省。

7. 系列总结

四篇下来,把 2000 万行 / 4.2GB 的同一份数据用三种方案各迁了一遍,全流程实测数据:

方案全流程导出冲击(源库TPS)适用量级(生产经验)上手难度
mysqldump + mysql source632s+12%(预热特例)<300GB
mydumper + myloader200s+4%~-13%300GB~15TB
xtrabackup 物理迁移369s-34%10TB+

没有银弹,四条实战心法:

  1. 量级定方案(生产经验):300GB 内逻辑单线程够用、15TB 内多线程逻辑最快、超大数据量物理迁移;单表迁移用可传输表空间
  2. 导出打哪儿很重要:主库导出冲击主库(P95 +17%),从库/ro 导出保护主库但会推高副本自身复制延迟(mysqldump 12s / mydumper 2s / xtrabackup 28s,工具越重延迟越高)——副本是否承担线上读流量决定这个 trade-off 怎么选
  3. 线程数有甜蜜点:4~8 线程最优,盲目加线程源库更疼、速度反而慢
  4. 导入调参三板斧(关 binlog、弱化刷盘、批量提交)能救 mysqldump 37%,但 myloader 默认配置已经站在调优起点上

最后说句实在的:本系列测的是”全量迁移”这一段,生产级迁移还包括增量追赶(binlog 复制)和割接(停写→追平→切流),那两步的坑不比全量少。云厂商 DTS 类产品把这些封装掉了,但理解了本系列的三种底层方案,你就知道 DTS 的”全量+增量”到底在做什么、为什么快、为什么贵。


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文由作者按照 CC BY 4.0 进行授权