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: YesSlave_SQL_Running: YesSeconds_Behind_Master: 0(趋近于0)Retrieved_Gtid_Set与Executed_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 保留策略,大幅降低操作风险):
- 源库设置为只读(应用程序需在连接层处理只读提示)
- 等待 GTID 增量同步追上(Seconds_Behind_Master=0 且 GTID 集合一致)
- ProxySQL 切换写流量到云数据库
- 在云数据库上执行 FLUSH LOGS 确认 binlog 位置
- 开始业务验证:运行关键查询,检查数据完整性
- 保留回滚窗口:源库保持只读状态至少 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 分钟窗口内平稳完成。