MyDumper并行重建MySQL只读副本实战:从3小时到25分钟
2026/9/19 3:49:45 网站建设 项目流程

上个月帮客户把一个约 800GB 的业务库从主库重建一个只读副本,第一版方案用 mysqldump,单线程导出,硬生生跑了 3 个多小时,还没算恢复时间。后来临时切到 MyDumper,8 线程并行导出,25 分钟完成,myloader 导入 40 分钟,整个重建链路从“要留一个通宵维护窗口”变成了“一顿午饭的时间”。这篇就把我这次实战里关于权限、参数、metadata 解析、复制配置、常见坑的完整记录写出来,给准备用 MyDumper 重建 MySQL 副本的同学一个可以直接抄的作业。

1. 先说结论:重建副本时 MyDumper 比 mysqldump 强在哪

1.1 mysqldump 在重建副本时的三个痛点

用 mysqldump 重建副本,大家最常走的套路是:

mysqldump --single-transaction --master-data=2 -A > all.sql

然后在从库上< all.sql导入。这个流程在几十 GB 的小库上问题不大,但库一大,三个痛点非常致命。

第一,导出是单线程的。mysqldump 无论表有多少,都是顺序一个个导出,单线程跑满一个 CPU 核心,其他核心全部闲着。第二,导入也是单线程的。生成的是一个巨型 SQL 文件,在从库执行时同样是单连接顺序执行,尤其每条 INSERT 都带多行 VALUES 时,解析和回放都慢。第三,不方便做数据表级拆分和压缩。mysqldump 也可以压缩,但基本都是整个文件一起压,断了没法续,单表出问题要整库重来。

1.2 MyDumper 的并行模型为什么适合副本重建

MyDumper 的理念很简单:导出时按表或按行数把任务拆成多个 chunk,每个 worker 线程各自连接数据库,并行把数据拉下来。导入时 myloader 同样开多个线程并行执行多个文件。这样导出、导入都是多线程,整体时间能压缩一个数量级。

它的工作模型大致是:主线程负责协调和锁一致性,worker 线程负责实际导表。每个表的 dump 又可以选择按固定行数拆分,拆成多个文件。这不仅是为了并行,更是为了恢复时的粒度控制——一个文件失败不用整库重来,单独处理那个文件即可。

1.3 一个真实的对比数据

我这次这个 800GB 实例大概是 2000 多张表,最大的单表 1.2 亿行。mysqldump 导出耗时 3 小时 10 分,导出文件约 420GB(未压缩)。后来换成 mydumper,8 线程导出,单表按 200 万行拆分,耗时 26 分钟,用-c压缩后只有 120GB。myloader 在目标实例上以 8 线程导入,耗时 45 分钟。这个差距在需要频繁做新副本、做环境交付、做 staging 环境同步的场景下,体验是完全不同的。

提示:如果你只是想偶尔备一个小库,mysqldump 完全够用,没必要为了用而用。但如果你的库超过 200GB、表数量上千,或者经常要重建副本,MyDumper 值得纳入工具箱。

2. 动手前的检查清单:权限、GTID、参数一页纸

2.1 备份账号的权限最小集

MyDumper 需要从主库读取数据、拿到 binlog 坐标、执行 FLUSH TABLES WITH READ LOCK(如果不用一致性快照则不需要 FTWRL)。所以备份账号的权限建议这样给:

权限作用
SELECT读取表数据
SHOW VIEW导出视图定义
TRIGGER导出触发器定义(若有)
RELOAD执行 FLUSH TABLES WITH READ LOCK
REPLICATION CLIENT执行 SHOW MASTER STATUS / SHOW REPLICA STATUS
BACKUP_ADMIN部分版本需要,用于获取一致的位点
LOCK TABLES早期版本 FTWRL 需要

MySQL 8.0 里如果主库开了caching_sha2_password认证,MyDumper 0.12 以上版本能正常支持,低版本可能会报认证插件不支持,建议直接装新版。

CREATE USER 'backup_user'@'%' IDENTIFIED BY 'YourStrongPass'; GRANT SELECT, SHOW VIEW, TRIGGER, RELOAD, REPLICATION CLIENT, BACKUP_ADMIN ON *.* TO 'backup_user'@'%'; FLUSH PRIVILEGES;

2.2 优先使用 GTID 复制

如果主库已经开启 GTID(gtid_mode=ON),强烈建议重建副本时走 GTID 方式。原因很简单:MyDumper 的 metadata 文件里会记录当前已执行或已导出的 GTID 集合,恢复完成后直接把从库的gtid_purged设置好,再 start replica 即可,不用关心 binlog 文件名和 position 的配对问题。

如果主库还没开 GTID,需要用传统的MASTER_LOG_FILEMASTER_LOG_POS方式,那就要在导出期间拿到准确的点并保持一致性。MyDumper 的 metadata 文件里都有记录,但要确保导出时不是通过--trx-consistency-only绕过 FTWRL 导致点不一致。

建议的检查命令:

SHOW VARIABLES LIKE 'gtid_mode'; SHOW MASTER STATUS;

2.3 常用参数速查

我在实际操作中常用的参数组合:

mydumper \ -h 主库IP -P 3306 \ -u backup_user -p '密码' \ -B 需要备份的库名 \ -o /data/backup/my_dump \ -r 2000000 \ -c \ -t 8 \ --trx-consistency-only \ --kill-long-queries \ --long-query-guard 120 \ --regex '^(?!(mysql\.|sys\.|information_schema\.|performance_schema\.))' \ -L mydumper.log \ -v 3

解释一下几个关键参数:

  • -r 2000000:每个数据文件最多 200 万行,超过就拆成下一个文件。这个值直接决定恢复时的并行粒度。
  • -c:输出时用压缩格式,myloader 导入时自动解压。
  • -t 8:导出线程数,一般按 CPU 核数和主库负载情况选。
  • --trx-consistency-only:不开 FTWRL,用 InnoDB 事务一致性读拿到快照,对在主库还跑着业务的情况更友好。
  • --regex:排除系统库,避免把mysql.user这种权限表也导出来造成元数据混乱。

3. 全量备份实战:一条 mydumper 命令跑出一个完整副本

3.1 备份工作目录和数据文件布局

执行 mydumper 后,输出目录里大概是这样的结构:

/data/backup/my_dump/ ├── metadata ├── mydb.schema.sql ├── mydb.table1.sql ├── mydb.table1.00001.sql ├── mydb.table1.00002.sql ├── mydb.table2.sql ├── mydb.table2.00001.sql

其中mydb.schema.sql是建库语句,mydb.table1.sql是建表语句(schema),mydb.table1.00001.sql是数据文件。如果表行数小于-r指定的值,则只会有一个数据文件。

3.2 metadata 文件到底记录了什么

metadata 是重建副本最关键的参考文件,我每次都要打开看一下:

cat /data/backup/my_dump/metadata

内容大致类似下面这样:

Started dump at: 2025-01-12 10:30:01 SHOW MASTER STATUS: Log: mysql-bin.000128 Pos: 44356621 GTID:1e7d5e1a-3f3c-11ee-9ff2-0242ac120002:1-43921, 2b1f6ff2-4d4e-11ef-a9a4-0242ac110003:1-1003 Finished dump at: 2025-01-12 10:31:55

注意几个字段的含义:

  • Started dump at:导出开始的时间,也是这个一致的快照时间点。
  • LogPos:传统复制的 binlog 坐标,从这个位置之后主库产生的 binlog 是从库需要继续追的增量。
  • GTID:GTID 集合,表示导出数据包含了这些事务,之后需要追的是集合之外的新事务。

3.3 备份期间主库 DDL 的处理

MyDumper 在导出单表时如果主库恰好对这张表做了 DDL,比如加列、删索引,最典型的表现是:某个 worker 线程报Table definition has changed, please retry transaction。这种错误通常只影响个别表,我的处理方式是先重新单独备份那一张表,或者如果时间允许,干脆在业务低峰期重跑一次全量。

另外,--trx-consistency-only依赖 RR 隔离级别下的START TRANSACTION WITH CONSISTENT SNAPSHOT,所以 InnoDB 表的快照是一致的,但 MyISAM 表在备份期间会被锁,这也是一个要提前确认的点。现在业务库基本都是 InnoDB,影响不大,但如果你库里还混着 MyISAM 表,要么提前迁移,要么做好锁表时间可能拉长的准备。

3.4 备份时的日志怎么看

mydumper 的日志如果开了-L指向文件,导出过程中可以实时观看进度。关键指标是每个表的 dump 是否完成,以及是否有 warning:

tail -f mydumper.log

如果你看到类似这样的一行:

** Message: 10:31:02 [INFO]: Queue 8, Table: mydb.big_table, Rows: 23405000, Filename: mydb.big_table.00001.sql

说明big_table已经拆了第一个文件,200 万行数据已经先落盘,继续往下拆。如果某张表的文件一直没有更新,往往说明它正在等待 DML 锁或主库查询资源紧张。

4. 在新实例上还原:myloader 导入和副本装配

4.1 新实例的前置准备

还原之前,目标实例要先初始化好。我的习惯是:

  • 版本尽量保持和主库一致,至少大版本一致(比如主库是 8.0.36,从库也装 8.0.x,别拿 5.7 去恢复 8.0 的数据)。
  • server-id必须和主库不同,这是复制配置的基础。
  • 如果是走 GTID,先把gtid_mode参数正确配置,如果主从都已开启 GTID,一般步骤是在配置文件中设置gtid-mode=ON等参数,然后重启实例。
  • 如果是从一个已运行过的实例来恢复,最好先RESET MASTER(确认该实例可以被重置时)避免孤儿 GTID 干扰。

4.2 myloader 导入命令

myloader \ -h 新从库IP -P 3306 \ -u restore_user -p '密码' \ -d /data/backup/my_dump \ -t 8 \ -B mydb \ -o \ --purge-mode=1
  • -d指定备份目录。
  • -t线程数,建议不要超过备份目录里的文件数,否则部分线程空闲。
  • -o覆盖已有表。
  • -B只恢复指定库,如果你的备份目录里有多个库而只想先恢复其中一个。
  • --purge-mode=1表示在导入前删除目标库中已存在的同名表,相当于“重建目标表”,适合副本重建场景。

导入过程中可以通过另一个终端监控目标库的负载,myloader 的日志也会显示每个线程处理了哪些文件。

tail -f /tmp/myloader.log

如果某个文件导入失败,myloader 通常会把 worker 线程继续处理其他文件,最后返回一个状态码。你需要回头单独导入失败文件。这种情况多数是因为表结构冲突或主键冲突,解决后再补一次即可。

4.3 恢复后的清理工作

导入完成后,有几个事情强烈建议做一下:

  • 检查库表数量是否和主库一致:对比SHOW TABLES数量、每张表的行数。
  • 检查触发器、视图、存储过程是否齐全:mydumper 默认会导出这些逻辑对象,但如果权限不够可能被静默跳过,日志里会有 warning。
  • 如果需要设置复制账号,提前建好repl_user
CREATE USER 'repl_user'@'%' IDENTIFIED BY 'ReplPass123'; GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl_user'@'%';

4.4 拿到复制起点

这一步要分两种情况。

GTID 模式:登录主库,执行:

SHOW MASTER STATUS;

把返回的Executed_Gtid_Set记下来,或者直接查看备份目录下 metadata 文件里的 GTID 字段。从库恢复完数据后,需要设置:

RESET MASTER; SET GLOBAL gtid_purged='...备份时导出的GTID集合...';

注意:gtid_purged的值必须包含你导入数据中所有已存在的事务。如果你之前在从库上有写入或误操作,gtid_purged可能因为 GTID 冲突而设置失败,这时需要更精细地清理。

传统 binlog 位置模式:直接使用 metadata 文件里的LogPos值。前提是主库的 binlog 还没被清除(binlog_expire_logs_seconds覆盖范围内),否则从库会报Got fatal error 1236

5. 配置复制并追上主库:从 Seconds_Behind_Source 说起

5.1 用 CHANGE REPLICATION SOURCE 指定主库

MySQL 8.0 官方推荐的新语法是CHANGE REPLICATION SOURCE TO,老版本语法CHANGE MASTER TO在 8.0 里仍然兼容,但新版本我更建议直接用新写法。

GTID 模式的配置:

CHANGE REPLICATION SOURCE TO SOURCE_HOST='主库IP', SOURCE_PORT=3306, SOURCE_USER='repl_user', SOURCE_PASSWORD='ReplPass123', SOURCE_AUTO_POSITION=1;

传统坐标模式的配置:

CHANGE REPLICATION SOURCE TO SOURCE_HOST='主库IP', SOURCE_PORT=3306, SOURCE_USER='repl_user', SOURCE_PASSWORD='ReplPass123', SOURCE_LOG_FILE='mysql-bin.000128', SOURCE_LOG_POS=44356621;

启动复制:

START REPLICA; SHOW REPLICA STATUS\G

5.2 判断追平进度的正确姿势

很多人只盯Seconds_Behind_Source,但这个值在刚启动复制时经常是 0 或者 NULL,很容易误判。实际判断追平与否要看两个:

  • Retrieved_Gtid_Set是否已经包含主库当前最新的 GTID 集合。
  • Seconds_Behind_Source稳定且接近 0,并且SQL_Delay为 0。

更直接的确认方法是:在主库执行一些小的写入(比如在一个测试表上 insert 一行),然后在从库查同样数据是否出现,延迟能控制在秒级就算追平。

SHOW REPLICA STATUS\G

重点看这几个字段:

字段期望值
Replica_IO_RunningYes
Replica_SQL_RunningYes
Retrieved_Gtid_Set持续向主库的最新 GTID 靠拢
Seconds_Behind_Source越小越好,稳定为 0 最佳
Last_IO_Error
Last_SQL_Error

5.3 追平后的数据校验

副本重建完,不能光看复制状态就上线,还要做一次数据校验。我的做法是抽几张大表做行数和部分列对比,再随机抽几张小表做全表校验。有 Percona Toolkit 环境的,可以直接用pt-table-checksum做主从数据一致性校验:

pt-table-checksum --host=主库IP --user=checksum_user --password=xxx --databases=mydb --replicate=percona.checksums

然后到从库上查percona.checksums里差异结果是0的表。如果某个表差异不为 0,优先检查该表最近的 DML 是否因为正则排除、触发器等导致漏数据或重复数据。绝大多数情况在正常使用 MyDumper 的一致性备份下,全量完成后校验都是一致的。

6. 踩坑集:这几个问题我都在生产环境里遇到过

6.1 权限不足导致触发器、视图被跳过

第一次用 MyDumper 时,备份账号我少给了一个TRIGGER权限,结果导出的 schema 文件里建表语句都正常,但触发器一个都没有。日志里有一堆 warning,当时没注意,恢复完去查触发器数量才发现少了。建议在备份前就用SHOW TRIGGERSSHOW FULL TABLES WHERE Table_type LIKE '%VIEW%'先确认主库上有多少这类对象,导出后再对比一次。

6.2--trx-consistency-only和长事务的关系

--trx-consistency-only其实不需要 FTWRL,而是依赖事务一致性读。但前提是主库上没有长时间未提交的事务。如果主库有一个跑了半小时的大事务,MyDumper 在导这张表时会一直等待这个事务释放行锁,表现为日志里某张表的文件长时间不更新,整体备份时间被拖长。

我遇到过极端情况:主库一个事务跑了一个多小时,MyDumper 一直卡在某张表上。解决方案是结合--kill-long-queries--long-query-guard限制超过阈值的长查询,但这个参数要慎用,小心把正常业务的大查询杀掉。我的建议是:非业务高峰期做全量备份,不要把--kill-long-queries开得那么激进;如果高峰期必须备份,可以把超时时间调大,避免误杀。

6.3 认证插件导致连接失败

MySQL 8.0 默认的认证插件是caching_sha2_password,如果 mydumper/myloader 编译时链接的客户端库比较老,连接时可能报:

ERROR 2061 (HY000): Authentication plugin 'caching_sha2_password' cannot be loaded

解决方式有两个:一是升级 MyDumper 到 0.12 以上版本并配套新版 libmysqlclient,二是对备份账号指定mysql_native_password认证:

ALTER USER 'backup_user'@'%' IDENTIFIED WITH mysql_native_password BY 'YourStrongPass';

但要注意 MySQL 8.4 起mysql_native_password默认被禁用,所以长期方案还是升级工具版本。

6.4 备份文件过多导致 inode 耗尽

我第二次用 MyDumper 时,表特别多,又按 10 万行拆了一次,结果整个备份目录生成了接近 50 万个文件。拷贝到从库时速度慢不说,还差点把磁盘分区 inode 耗尽。建议:

  • df -i提前检查备份目录所在分区的 inode 数量。
  • 单表行数很大的表才需要拆成多文件,小表不需要拆也无法拆。
  • -r值不要设得太小,我一般 100 万到 300 万行一个文件比较平衡。

6.5 部分库备份恢复后复制起点不完整

-B指定备份某个库时,metadata 里记录的是主库当时的全局 binlog 位置和 GTID。恢复单库后,如果用这个全局位置去配置复制,从库去重放 binlog 时会听到主库上其他库的写入,比较容易出现主键冲突或“上一次事务从库已经存在”之类的问题。

如果确实只需要某个库的副本,建议在主库上对该库单独从业务层面确认没有其他库的写入依赖,或者干脆在从库复制链路里加上CHANGE REPLICATION FILTER (REPLICATE_DO_DB = ('mydb'))这样的过滤。不过过滤规则要小心,毕竟复制过滤会影响级联复制下的行为,生产环境尽量避免。

6.6 myloader 导入时主键冲突常见原因

从备份恢复到停写的测试库,一般不会有主键冲突。但在一个还有业务的实例上恢复,或者之前导入过一次只覆盖了部分表,就容易出现:

ERROR 1062 (23000): Duplicate entry 'xxx' for key 'PRIMARY'

我的处理建议是导入前先明确目标实例要处于“纯净”状态,--purge-mode=1配合使用。如果做副本重建,新实例里不应该有任何业务数据。如果确实要保留部分表,请用-B配合--only-schema--skip-triggers等参数,按需导入,而不是盲目整库覆盖。

7. 实例再大一档时的调优思路和自动化扩展

7.1 调整线程数与-r的配合

线程数的选择,不是越大越好。我在 16 核的备机上做过测试:8 线程和 16 线程导出时间差别不大,但 16 线程对主库造成的压力明显更高,尤其是大量随机读容易打高 IOPS。常见的经验值是 4 到 8 线程起步,观察主库SHOW PROCESSLIST里的State=System lock和磁盘 IO 再往上调。

-r的值如果设得太小,文件数量会爆炸;设得太大,恢复时线程容易因为一个大文件而出现“尾巴”——比如一个 5000 万行的表被拆成 20 个文件,其中最后一个文件只有几十行,恢复时某些线程提前空闲,整体时间仍被最大文件拖住。我用 200 万行左右做基准,再根据表行数分布微调。

7.2 备份完成后立刻做一次快速校验

除了全量结束后用 pt-table-checksum,我还会在导入阶段完成后做一个非常快的校验:对比主库和从库的information_schema.tables里每张表的行数。可以用一条 SQL 完成:

SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema='mydb' ORDER BY table_name;

注意table_rows是估算值,不是精确值,只适合用来发现明显异常,比如某张表完全没导入或行数差了一个数量级。真正要精确校验还是要COUNT(*)pt-table-checksum

7.3 把整个流程脚本化

如果重建副本的频率不低,建议把整个流程扣成脚本:备份、拷贝、初始化从库、导入、配置复制、校验。流程里的可变量用参数传递。我自己的脚本里核心步骤大概是:

  1. 主库执行 mydumper,保留 metadata 文件。
  2. 通过 rsync 增量同步备份目录到目标机器,只传新增文件。
  3. 目标机器初始化 MySQL 实例。
  4. myloader 导入全量备份。
  5. 根据 metadata 读取 GTID 或 binlog 坐标。
  6. 配置复制并 START REPLICA。
  7. 循环检查追平状态。
  8. 追平后执行 pt-table-checksum 并输出报告。

脚本跑起来后,副本重建就变成一个可以随时触发的跑批任务,而不是每次都要人肉盯一天。

7.4 最后分享一个小技巧

如果你担心备份文件在传输过程中被改动或者磁盘故障,建议加上--checksum参数,让 mydumper 在备份时同时生成校验文件。恢复完成后,在备份目录里执行校验脚本,可以快速确认目录内文件是否完整。这个习惯帮我避免过至少两次因为 NFS 传输丢文件而导致的导入失败。

用 MyDumper 重建 MySQL 副本,本质上就是用“并行 + 拆分”的思路把传统单线程的大库迁移改造成可横向扩展的流水线。配合 GTID、metadata 分析和复制配置,对几百 GB 甚至 TB 级的实例来说,操作体验和维护成本都会有一个明显的提升。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询