MySQL云数据库迁移实战:Mydumper并行导出+GTID增量同步+GTID_PURGED预检+回滚沙盘

1. 迁移场景与架构总览

源库:自建MySQL 8.0,单机部署,数据量约800GB,包含数百张InnoDB表,存在外键和压缩表。

目标库:云数据库RDS(MySQL 8.0),开启GTID模式,binlog保留7天。

核心要求:业务停机窗口不超过15分钟,迁移失败需具备5分钟内回滚能力。

整体链路:

  • Mydumper 并行全量导出 + --trx-consistency-only 保证一致性快照
  • Myloader 并行导入目标云数据库
  • 基于 GTID 的增量 Binlog 同步(master_auto_position=1)
  • pt-table-checksum 全量一致性校验 + 增量校验脚本
  • 回滚沙盘:源库保留只读副本 + ProxySQL 路由切换

2. 源库准备:GTID模式开启与权限收敛

务必检查源库是否已启用 GTID,否则需要动态修改(注意大事务风险):

-- 检查当前GTID状态
SHOW VARIABLES LIKE 'gtid_mode';
SHOW VARIABLES LIKE 'enforce_gtid_consistency';

-- 动态开启(分三步,避免报错)
SET @@GLOBAL.enforce_gtid_consistency = ON;
SET @@GLOBAL.gtid_mode = ON_PERMISSIVE;
SET @@GLOBAL.gtid_mode = ON;

创建迁移专用用户,权限最小化:

CREATE USER 'migrate_user'@'%' IDENTIFIED BY 'StrongP@ss2024';
GRANT SELECT, RELOAD, PROCESS, LOCK TABLES, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'migrate_user'@'%';
FLUSH PRIVILEGES;

坑点1: 云数据库侧如果开启了 --read-only,需要先关闭,否则 Myloader 写入会报错。

坑点2: 源库 binlog_format 必须是 ROW,否则 GTID 增量同步会卡住不报错。

3. Mydumper 并行全量导出:参数调优与GTID位置捕获

使用 Mydumper 0.16+ 版本,支持压缩表和外键感知。关键参数解析:

myduper -u migrate_user -p 'StrongP@ss2024' \
  -B dbname \
  -t 12 \
  -c \
  --trx-consistency-only \
  --innodb-optimize-keys \
  --rows=500000 \
  --chunk-filesize=256 \
  --source-control \
  -o /data/mydumper_backup
  • -t 12:并发线程数,根据源库 CPU 核数调整,过大会导致锁竞争
  • -c:压缩导出,减少磁盘IO和网络传输量
  • --trx-consistency-only:仅使用事务一致性,不锁表(InnoDB 专用)
  • --innodb-optimize-keys:导出时优化索引顺序,减少导入时的排序开销
  • --rows=500000:每个chunk的行数,控制内存占用
  • --source-control:导出完成后生成 metadata 文件,包含 GTID 位置

导出完成后,立即备份 metadata 文件:

cat metadata
# 输出示例:
# Started dump at: 2024-11-20 14:32:10
# SHOW MASTER STATUS:
#     Log: mysql-bin.003412
#     Pos: 829173847
#     GTID: d44a3c22-1a2b-11ef-8e7f-0050568f3c2d:1-39872144

冷门技巧: 如果源库有 压缩表(ROW_FORMAT=COMPRESSED),Mydumper 导出时默认使用 --innodb-optimize-keys 会导致索引统计不准,建议加上 --skip-innodb-optimize-keys 并在导入后手动 ANALYZE TABLE。

4. Myloader 导入云数据库:线程池与内存调优

myloader -u cloud_admin -p 'CloudP@ss2024' \
  -h cloud.rds.aliyuncs.com \
  -B dbname \
  -t 16 \
  -d /data/mydumper_backup \
  --enable-binlog \
  --innodb-optimize-keys \
  --overwrite-tables
  • -t 16:导入并发数,云数据库IOPS较高时可适当增大
  • --enable-binlog:导入过程中生成 binlog,便于后续增量同步追赶
  • --overwrite-tables:如果表已存在则先DROP再CREATE

坑点3: 目标云数据库如果开启了 大事务限制(如 max_binlog_cache_size 较小),导入大表会报错 Multi-statement transaction required more than 'max_binlog_cache_size'。解决方案:分批导入或临时调大参数:

SET GLOBAL max_binlog_cache_size = 8 * 1024 * 1024 * 1024;  -- 8GB

坑点4: 外键表导入顺序错误导致失败。Myloader 默认按字母序导入,如果存在外键依赖,需手动拆分 schema 文件,先导主表再导子表。这里提供快速检测脚本:

# 导出外键依赖关系
mysql -u migrate_user -p -B dbname -e "
  SELECT TABLE_NAME, REFERENCED_TABLE_NAME
  FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
  WHERE TABLE_SCHEMA='dbname' AND REFERENCED_TABLE_NAME IS NOT NULL
  ORDER BY TABLE_NAME;" > fk_dep.txt

5. GTID 增量同步:GTID_PURGED 预检与校准

这是整个迁移最关键的环节,99%的失败都出在 GTID_PURGED 未对齐。

5.1 在云数据库上设置 GTID_PURGED

-- 在目标云数据库上执行
RESET MASTER;
SET GLOBAL GTID_PURGED = 'd44a3c22-1a2b-11ef-8e7f-0050568f3c2d:1-39872144';

冷门深度点: GTID_PURGED 不是直接 SET 就能成功的,必须满足:

  • gtid_executed 必须为空(即刚 RESET MASTER 之后)
  • gtid_purged 的值必须是 gtid_executed 的超集
  • 如果目标库已经有过写入(比如 Myloader 生成了 GTID),则 GTID_PURGED 会失败,报错 GTID_PURGED can only be set when gtid_executed is empty

解决方案: 在 Myloader 导入前先清空目标库的 GTID_EXECUTED:

-- 方法1:RESET MASTER(会丢失所有 binlog,仅适用于全新实例)
RESET MASTER;

-- 方法2:如果目标库已有业务数据,使用 --skip-gtid-reset 配合手动设置
-- 在 Myloader 导入时加 --skip-gtid-reset,导入完成后:
SET GLOBAL GTID_PURGED = 'd44a3c22-1a2b-11ef-8e7f-0050568f3c2d:1-39872144';
-- 然后 CHANGE MASTER 会自动识别 GTID 位置

5.2 配置增量同步

CHANGE MASTER TO
  MASTER_HOST='source_host_ip',
  MASTER_PORT=3306,
  MASTER_USER='migrate_user',
  MASTER_PASSWORD='StrongP@ss2024',
  MASTER_AUTO_POSITION=1;

START SLAVE;

-- 检查同步状态
SHOW SLAVE STATUS\G

重点关注:

  • Slave_IO_Running: Yes
  • Slave_SQL_Running: Yes
  • Seconds_Behind_Master: 0(趋近于0)
  • Retrieved_Gtid_SetExecuted_Gtid_Set 持续增长且匹配

5.3 GTID 不一致的应急处理

如果遇到 Slave_SQL_Running: No 且错误为 GTID_NEXT cannot be set to AUTOMATIC,说明 GTID 已经错乱。此时不要手动跳事务,而是使用 pt-slave-restart 自动跳过已知错误:

pt-slave-restart --host=cloud.rds.aliyuncs.com --user=cloud_admin --password=CloudP@ss2024 --error-numbers=1062,1032

如果是 1032 错误(update/delete 行不存在),说明源库和云数据库数据不一致,必须用 pt-table-checksum 做全量校验。

6. 数据一致性校验:pt-table-checksum + 增量校验脚本

6.1 全量校验

pt-table-checksum \
  --host=cloud.rds.aliyuncs.com \
  --user=cloud_admin \
  --password=CloudP@ss2024 \
  --databases=dbname \
  --replicate=percona.checksums \
  --create-replicate-table \
  --no-check-binlog-format \
  --chunk-size=2000 \
  --max-load='Threads_running=50'

参数说明:

  • --replicate=percona.checksums:校验结果写入该表,便于增量对比
  • --chunk-size=2000:每个chunk行数,避免大表锁住
  • --max-load:当线程数超过50时暂停,避免影响业务

校验完成后查询不一致的表:

SELECT db, tbl, SUM(this_cnt) AS total_rows,
       SUM(this_cnt - master_cnt) AS diff_rows
FROM percona.checksums
WHERE master_cnt != this_cnt OR this_crc != master_crc
GROUP BY db, tbl;

6.2 增量校验脚本(冷门实战代码)

由于 pt-table-checksum 是静态校验,增量同步过程中需要持续监控。以下脚本每10秒对比一次最新 GTID 位置:

#!/bin/bash
# incremental_gtid_check.sh
SOURCE_HOST="source_host_ip"
CLOUD_HOST="cloud.rds.aliyuncs.com"
USER="migrate_user"
PASS="StrongP@ss2024"

while true; do
  src_gtid=$(mysql -u$USER -p$PASS -h$SOURCE_HOST -e "SHOW MASTER STATUS\G" | grep "Executed_Gtid_Set" | awk '{print $2}')
  cloud_gtid=$(mysql -u$USER -p$PASS -h$CLOUD_HOST -e "SHOW SLAVE STATUS\G" | grep "Executed_Gtid_Set" | awk '{print $2}')

  # 比较 GTID 集合是否相同
  if [ "$src_gtid" != "$cloud_gtid" ]; then
    echo "[WARN] $(date) GTID mismatch!"
    echo "Source: $src_gtid"
    echo "Cloud:  $cloud_gtid"
  else
    echo "[OK] $(date) GTID consistent"
  fi

  # 检查 slave 延迟
  seconds_behind=$(mysql -u$USER -p$PASS -h$CLOUD_HOST -e "SHOW SLAVE STATUS\G" | grep "Seconds_Behind_Master" | awk '{print $2}')
  if [ "$seconds_behind" -gt 10 ]; then
    echo "[ALERT] $(date) Replication lag: ${seconds_behind}s"
  fi

  sleep 10
done

7. 回滚沙盘机制:ProxySQL + 只读副本

迁移到云数据库后,如果出现性能问题或数据不一致,需要快速回滚到源库。关键在于:源库在迁移期间不能写入新数据,否则回滚时会丢失增量数据。

7.1 源库设置只读 + 保留增量Binlog

-- 在业务流量切换到云数据库之前,将源库设置为只读
SET GLOBAL read_only = ON;
SET GLOBAL super_read_only = ON;

-- 确保 binlog 保留至少 3 天(用于回滚时的增量补全)
SET GLOBAL expire_logs_days = 3;

7.2 ProxySQL 路由切换脚本

使用 ProxySQL 作为中间层,实现秒级流量切换:

# 写入 ProxySQL 配置
mysql -u admin -padmin -h 127.0.0.1 -P 6032 <<'EOF'
-- 配置源库和后端
INSERT INTO mysql_servers (hostgroup_id, hostname, port) VALUES (1, 'source_host_ip', 3306);
INSERT INTO mysql_servers (hostgroup_id, hostname, port) VALUES (2, 'cloud.rds.aliyuncs.com', 3306);

-- 配置查询规则:默认读写分离
INSERT INTO mysql_query_rules (rule_id, active, match_pattern, destination_hostgroup, apply) VALUES (1, 1, '^SELECT', 2, 1);
INSERT INTO mysql_query_rules (rule_id, active, match_pattern, destination_hostgroup, apply) VALUES (2, 1, '.*', 1, 1);

LOAD MYSQL SERVERS TO RUNTIME;
LOAD MYSQL QUERY RULES TO RUNTIME;
SAVE MYSQL SERVERS TO DISK;
SAVE MYSQL QUERY RULES TO DISK;
EOF

回滚操作: 只需将写请求的 hostgroup 从 2 改回 1:

UPDATE mysql_query_rules SET destination_hostgroup=1 WHERE rule_id=2;
LOAD MYSQL QUERY RULES TO RUNTIME;

同时停止增量同步,并将云数据库侧未同步的 binlog 手动应用回源库(使用 mysqlbinlog):

mysqlbinlog --read-from-remote-server --host=cloud.rds.aliyuncs.com --user=cloud_admin --password=CloudP@ss2024 --stop-never mysql-bin.003413 | mysql -u root -p -h source_host_ip

8. 割接流程与监控清单

正式割接时,按以下步骤执行(以轻云互联云数据库RDS为例,其控制台支持一键查看 GTID 状态和 binlog 保留策略,大幅降低操作风险):

  1. 源库设置为只读(应用程序需在连接层处理只读提示)
  2. 等待 GTID 增量同步追上(Seconds_Behind_Master=0 且 GTID 集合一致)
  3. ProxySQL 切换写流量到云数据库
  4. 在云数据库上执行 FLUSH LOGS 确认 binlog 位置
  5. 开始业务验证:运行关键查询,检查数据完整性
  6. 保留回滚窗口:源库保持只读状态至少 72 小时,确认无问题后再关闭

监控重点:

  • Threads_connected 是否异常升高
  • Innodb_rows_read / Innodb_rows_inserted 比值是否稳定
  • Slave_IO_Running / Slave_SQL_Running 保持 Yes
  • 云数据库 CPU 使用率不超过 80%,IOPS 不超过限额的 70%

9. 总结:一次迁移的成败清单

最后给出一份迁移前后必须检查的清单,直接贴到运维手册里:

# 迁移前检查
□ 源库 GTID 已开启且 binlog_format=ROW
□ 目标云数据库 max_binlog_cache_size ≥ 4GB
□ Mydumper metadata 文件已备份
□ 目标云数据库 RESET MASTER 后 GTID_PURGED 已设置

# 迁移中监控
□ pt-table-checksum 全量校验通过(diff_rows=0)
□ 增量同步 Seconds_Behind_Master < 5s
□ GTID_PURGED 预检脚本每10秒输出 OK
□ ProxySQL 路由规则已配置

# 迁移后回滚准备
□ 源库 read_only=ON
□ 源库 expire_logs_days ≥ 3
□ ProxySQL 回滚脚本已测试
□ 回滚沙盘(源库+只读副本)已就绪

这份清单在轻云互联的云数据库迁移项目中经过多次验证,核心就是 GTID_PURGED 预检 + 增量校验脚本 + ProxySQL 秒级回滚 三个锚点。只要这三点不出问题,10TB 级别的迁移也能在 15 分钟窗口内平稳完成。