踩坑记录:生产环境MySQL迁移那些事

上个月接到个任务,把公司的MySQL从阿里云迁移到自建机房。听起来简单,mysqldump导出导入不就完了?

结果折腾了整整一周,踩了无数坑。记录一下,给后来人避雷。

背景

原来的数据库在阿里云RDS,配置是4核8G,数据量大概200G。要迁移到公司自建机房的物理服务器上。

要求:

  • 停机时间控制在30分钟内
  • 不能丢数据
  • 迁移后性能不能下降

第一个坑:mysqldump太慢了

最开始想的最简单,直接mysqldump:

mysqldump -h rds.xxx.com -u root -p --all-databases > all.sql

跑了6个小时还没导完...

200G的数据,单线程dump,速度大概10MB/s。算了一下,导出要6小时,导入至少也要4-5小时。光这一步就10多个小时,停机30分钟?做梦。

解决:mydumper多线程导出

# 安装mydumper
apt install mydumper

# 多线程导出(8线程)
mydumper -h rds.xxx.com -u root -p password \
  -t 8 \
  -c \
  -o /backup/dump

# 多线程导入
myloader -h localhost -u root -p password \
  -t 8 \
  -d /backup/dump

8线程跑,导出时间从6小时降到了50分钟。导入也快了很多。

第二个坑:字符集不一致

数据导入后,发现有些中文变成了问号。

排查了半天,发现阿里云RDS默认是utf8mb4,我们自建的MySQL是utf8

-- 检查字符集
SHOW VARIABLES LIKE 'character%';

阿里云:

character_set_database: utf8mb4
character_set_server: utf8mb4

自建:

character_set_database: utf8
character_set_server: utf8

解决:统一字符集

# my.cnf
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci

[client]
default-character-set=utf8mb4

然后重新导入。这次中文正常了。

第三个坑:自增ID冲突

导入完成后,应用跑起来,几分钟后报错:

Duplicate entry '12345' for key 'PRIMARY'

自增ID冲突了?

原因是导入时没有导入AUTO_INCREMENT的值,新插入的数据从1开始自增,和原有数据冲突了。

解决:导出时带上自增值

mydumper -h rds.xxx.com -u root -p password \
  -t 8 \
  --set-names=utf8mb4 \
  --triggers \
  --routines \
  --events \
  -o /backup/dump

或者手动修复:

-- 查看当前最大ID
SELECT MAX(id) FROM users;
-- 结果:98765

-- 设置AUTO_INCREMENT
ALTER TABLE users AUTO_INCREMENT = 100000;

第四个坑:binlog位置找不到

迁移过程中,数据还在持续写入。导出时记录了binlog位置,准备用binlog追数据。

但是发现阿里云RDS的binlog只保留7天,而且位置格式和自建MySQL不一样。

解决:用GTID

还好阿里云RDS支持GTID,改用GTID来追数据:

# 导出时记录GTID
mydumper ... --set-names=utf8mb4

# 查看导出的metadata文件,里面有GTID信息
cat /backup/dump/metadata

导入后,配置从库从主库同步:

CHANGE MASTER TO
  MASTER_HOST='rds.xxx.com',
  MASTER_USER='repl',
  MASTER_PASSWORD='password',
  MASTER_AUTO_POSITION=1;

START SLAVE;

这样新数据就能自动同步过来了。

第五个坑:外键约束导入失败

有几张表死活导不进去,报错:

Cannot add or update a child row: a foreign key constraint fails

外键约束的表,导入顺序不对就会失败。

解决:临时关闭外键检查

SET FOREIGN_KEY_CHECKS=0;
-- 导入数据
source /backup/dump.sql
SET FOREIGN_KEY_CHECKS=1;

或者用myloader时加参数:

myloader -h localhost -u root -p password \
  -t 8 \
  --overwrite-tables \
  -d /backup/dump

myloader会自动处理外键顺序。

第六个坑:迁移后查询变慢

数据迁移完成,应用跑起来,发现有些查询特别慢。

原来在阿里云RDS上只要100ms的查询,现在要5秒。

用EXPLAIN看了一下,发现没走索引。再一看,索引是有的,但是没生效。

原因:统计信息过期

数据导入后,表的统计信息还是旧的,优化器选错了执行计划。

-- 更新统计信息
ANALYZE TABLE users;
ANALYZE TABLE orders;

-- 或者批量更新所有表
mysqlcheck -u root -p --analyze --all-databases

跑完之后,查询速度恢复正常。

第七个坑:时区问题

有些时间字段差了8小时。

阿里云RDS默认时区是Asia/Shanghai,我们自建的是UTC

-- 检查时区
SHOW VARIABLES LIKE '%time_zone%';

解决:统一时区

# my.cnf
[mysqld]
default-time-zone = '+08:00'

最终的迁移方案

踩完这些坑,最后总结出来的迁移步骤:

1. 准备阶段(T-1天)

# 在新服务器上配置好MySQL,确保:
# - 字符集:utf8mb4
# - 时区:+08:00
# - 开启GTID

2. 全量同步

# mydumper导出
mydumper -h rds.xxx.com -u root -p password \
  -t 8 \
  --set-names=utf8mb4 \
  --triggers --routines --events \
  -o /backup/dump

# myloader导入
myloader -h localhost -u root -p password \
  -t 8 \
  --overwrite-tables \
  -d /backup/dump

3. 增量同步

-- 配置主从复制
CHANGE MASTER TO
  MASTER_HOST='rds.xxx.com',
  MASTER_USER='repl',
  MASTER_PASSWORD='password',
  MASTER_AUTO_POSITION=1;

START SLAVE;

-- 确认同步状态
SHOW SLAVE STATUS\G
-- Seconds_Behind_Master: 0 表示追上了

4. 切换(停机时间)

# 1. 应用停写(或切只读)
# 2. 等待从库追上(Seconds_Behind_Master=0)
# 3. 停止复制
STOP SLAVE;
# 4. 修改应用配置,连接新数据库
# 5. 应用恢复

实际停机时间:不到10分钟。

异地迁移的额外问题

后来又做了一次跨城市的迁移,从北京机房到上海机房。

遇到的问题是:两个机房网络不通,北京是内网IP,上海访问不了。

试了几个方案:

  1. 开公网 - 安全风险大,被否了
  2. VPN - 配置复杂,而且VPN带宽不够
  3. 专线 - 太贵,临时迁移不值当

最后用的组网软件(星空组网),把两边服务器组到一个虚拟局域网里:

北京MySQL:10.26.0.1:3306
上海服务器:10.26.0.2

# 上海服务器直接连北京的MySQL
mysql -h 10.26.0.1 -u root -p

这样主从复制的配置也不用改,直接用内网IP就行。迁移完成后把组网关掉就行。

总结

MySQL迁移看似简单,实际坑不少:

解决方案
mysqldump慢 mydumper多线程
字符集不一致 统一utf8mb4
自增ID冲突 导出时带AUTO_INCREMENT
binlog追不上 用GTID
外键导入失败 关闭外键检查
查询变慢 ANALYZE TABLE
时区问题 统一时区设置
跨网络迁移 组网打通

如果你也要做MySQL迁移,建议先在测试环境完整跑一遍,把坑踩完再上生产。


有问题欢迎评论区交流。


posted @ 2025-12-22 09:17  花宝宝  阅读(0)  评论(0)    收藏  举报