MySQL迁移操作手册:一次完整迁移的实战路径

很多人以为数据库迁移就是"把数据导出来,再导进去"。实际上,这中间的每一个环节都可能埋雷:字符集对不上,数据导出来全是乱码;索引没重建,上线后查询慢得离谱;没留回滚方案,一出问题直接抓瞎。我经历过几次生产环境的MySQL迁移,从MySQL 5.6升级到8.0、从MySQL迁到PostgreSQL兼容系的国产数据库,每次踩的坑不一样,但迁移的核心逻辑是一样的。这篇手册不讲概念,直接讲怎么做、先后顺序是什么、哪里容易出错


一、迁移前:评估和准备比迁移本身更重要

迁移前的工作做扎实,后面能省一半麻烦。但现实往往是:业务方催着上线,开发没时间做充分评估,结果上线后问题一个接一个。

第一步,确认迁移的真实原因。 很多人说"MySQL性能不够了",但实际上用Explain跑一圈慢查询日志,发现80%的问题来自缺失索引或者SQL本身写得烂,换数据库解决不了根本问题。先用pt-query-digest或者MySQL慢查询日志跑一遍,锁定真正需要换库的场景。数据量上了TB、日均写入超过百万级别、单机已经出现存储瓶颈------这些是真正需要考虑迁移的信号。

第二步,确认停机窗口。 这决定了迁移方案的选择。如果业务允许2小时以上的停机窗口,用mysqldump导出导入就够了;如果只能接受分钟级停机,必须走主从复制或者双写方案。选型之前不确认这个,后面会很被动。

第三步,全面备份。 不管你打算用什么方案,迁移前必须做一次完整备份。建议同时做逻辑备份(mysqldump)和物理备份(xtrabackup),双重保险。备份文件检查完整性再往下走------很多团队备份做了,但上线前发现备份文件损坏,这时候没有回头路。

第四步,梳理依赖关系。 存储过程、触发器、事件调度器、外部表(FEDERATED)、分区表------这些在MySQL里能用,但迁移到其他数据库时往往是最费时间的部分。建议提前把这些对象单独列出来,提前评估改写工作量。我见过最惨的案例:一套业务系统有300多个存储过程,迁移前没盘点,上线前一周才发现改写工作量是两周,直接导致项目延期。

第五步,确认字符集。 迁移环节中,字符集问题能吃掉30%以上的时间。很多老库是latin1存的UTF-8数据,靠客户端"硬解"才显示正常,这种库直接mysqldump导出来大概率乱码。正确的做法是先在源库执行:

sql 复制代码
SHOW VARIABLES LIKE 'character_set%';

然后查每个库、每张表的真实字符集:

sql 复制代码
SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME 
FROM information_schema.SCHEMATA 
WHERE SCHEMA_NAME = 'your_db';

如果返回utf8,注意它不是真正的UTF-8,只能存3个字节,emoji和生僻字会截断。迁移到目标库之前,全部升级到utf8mb4


二、目标数据库选型:不同方案的门道

选型决定了后续所有工作的基调。MySQL迁移的常见目标库有这么几条路,各有各的适用场景和坑。

国产商业数据库。 很多政企项目有明确的信创要求,这时候会考虑电科金仓KingbaseES这类成熟的国产商业数据库。它同时提供Oracle兼容模式和MySQL兼容模式,对MySQL迁移来说需要特别留意兼容模式的选择------MySQL模式下语法覆盖程度直接影响业务改写量。另外,电科金仓在医疗、交通、金融、政府、央企这些领域里用得都比较多,和这类客户现有的技术栈对接起来往往比互联网开源方案顺畅,但前期的兼容性测试还是要做充分,不要因为看到"兼容MySQL"就默认可以无缝跑。

分布式数据库。 适合数据量上了TB、并发量非常大、对可用性要求极高的场景。TiDB、OceanBase是这条路的主要选择。优点是对MySQL协议兼容性好;缺点是运维复杂度高、资源消耗大,至少需要3节点部署。如果原来就是单机MySQL几十GB数据量,分布式数据库是典型的过度设计,先考虑读写分离或者分库分表更务实。

云厂商托管数据库。 阿里云PolarDB、腾讯云TDSQL属于这类,本质上是云厂商帮你处理了高可用和扩展性,但绑定了云生态。迁移出来成本会很高,选之前要想清楚这个锁定风险。

我的建议是:迁移前先在测试环境跑完整的业务回归用例,不要只测"能不能跑起来",要测实际业务场景的数据正确性和并发性能。选型报告可以写得漂亮,但生产数据不会说谎。


三、数据导出:mysqldump的参数决定后续的坑有多少

mysqldump看起来是个简单的命令,但参数用错一个,后面就是大麻烦。

标准导出参数配置:

bash 复制代码
mysqldump -h源库IP -u用户名 -p \
  --default-character-set=utf8mb4 \
  --single-transaction \
  --quick \
  --set-gtid-purged=OFF \
  --master-data=2 \
  --flush-logs \
  --triggers \
  --routines \
  --events \
  --hex-blob \
  --max_allowed_packet=67108864 \
  --B 数据库名 > backup_$(date +%Y%m%d).sql

几个关键参数说明:

--single-transaction对InnoDB表可以保证一致性,对MyISAM表则需要用--lock-tables,如果混合引擎库直接用--single-transaction会有数据不一致风险。

--set-gtid-purged=OFF解决GTID模式下导出的SQL文件里包含SET @@SESSION.GTID_PURGED语句的问题------如果目标库没有配置GTID,这句会直接导致导入失败。

--hex-blob把二进制字段导出为十六进制,避免特殊字符导致SQL语法错误。

如果源库里有MyISAM引擎的表,备份期间会锁表------这在生产环境上往往是灾难性的。最好的办法是在迁移窗口前把MyISAM表逐步改为InnoDB,如果业务不允许,导出时用--lock-tables并安排在业务低峰期执行。


四、数据导入:导出只是开始,导入才是真正的战场

数据导入不是简单跑mysql < backup.sql就完事了。

导入前,先检查目标库环境。 确认目标库的版本、字符集配置、max_allowed_packet参数是否足够大(建议不小于64MB)、InnoDB相关配置是否适配大表。如果是国产商业数据库,还要额外关注页大小配置、兼容模式参数,这些往往需要在配置文件里单独调整,用默认参数直接跑容易出问题。

导入顺序:先结构,后数据。--no-data参数单独跑一遍DDL,确认表结构和索引都正确建立,然后再导入数据。数据量大的时候,用--no-create-db配合--skip-add-drop-table逐库逐表导入,能更好地控制节奏,出问题也好定位。

bash 复制代码
# 导出结构(无数据)
mysqldump -h源 -u用户 -p --default-character-set=utf8mb4 \
  --no-data --set-gtid-purged=OFF --routines --triggers --events \
  --B 数据库名 > schema.sql

# 导出数据(无结构)
mysqldump -h源 -u用户 -p --default-character-set=utf8mb4 \
  --no-create-info --set-gtid-purged=OFF --skip-triggers \
  --B 数据库名 > data.sql

导入时带上字符集参数:

bash 复制代码
mysql --default-character-set=utf8mb4 -h目标 -u用户 -p 目标库名 < backup.sql

导入后第一件事,用这条SQL核验数据行数:

sql 复制代码
SELECT table_name, table_rows 
FROM information_schema.tables 
WHERE table_schema = 'your_db' 
  AND table_type = 'BASE TABLE';

和源库的行数做对比,差距超过1%就要追查原因。


五、迁移后验证:漏了这一步,前面全白干

数据导进去了不代表迁移成功了。迁移后验证是最容易被跳过的环节,也是最能发现问题的环节。

数据一致性校验 ,用pt-table-checksum跑核心表,对比源库和目标库的Checksum值,有差异的表逐行查原因。抽样用MD5验证关键字段的原始值,确保没有数据截断或乱码。

bash 复制代码
pt-table-checksum h=源库IP,u=root,p=密码 --replicate=checksums.dsn \
  --databases=your_db

索引有效性验证 。导入数据时索引是空的,如果不手动ANALYZE TABLE,查询优化器会用错误的统计信息生成执行计划:

sql 复制代码
ANALYZE TABLE your_db.table_name;

上线后跑一遍核心SQL的EXPLAIN,确认执行计划正常,索引被正确命中。

触发器、存储过程验证 。触发器默认带DEFINER属性,目标库如果不存在对应账号,导入后触发器会失效但不会报错:

sql 复制代码
SHOW TRIGGERS FROM your_db;
SELECT name, definer, body FROM mysql.proc WHERE db = 'your_db';

检查返回结果,如果definer指向的账号在目标库不存在,需要重建这些对象或修改DEFINERCURRENT_USER。在国产商业数据库上,有时候DEFINER问题还会碰到函数权限问题,需要额外授权。

连接测试和压测。 用应用账号连接目标库,确认权限配置正确。然后在测试环境跑完整的业务场景回归,重点测试写入操作、事务、并发场景。国产数据库的连接池配置有时候和MySQL有差异,JDBC参数需要根据目标库的要求做调整。


六、回滚方案:上线前必须准备好的兜底方案

回滚方案不是"停机回退"这么简单,它需要精确到哪个时间点可以切回旧库、切回后数据怎么对齐

推荐的做法是:上线后保持旧库处于只读同步状态,同时开启新库的写入通道。观察24到48小时,确认业务平稳后,再关闭旧库写入入口。这种方式的好处是:如果新库出问题,切换回旧库的数据丢失量最小。

回滚脚本最好在上线前就写好并测试过,而不是出了问题再临时抱佛脚。脚本里至少包含:关闭新库写入通道、恢复旧库为读写模式、验证数据一致性这几步。


七、一个值得强调的迁移原则

MySQL迁移这件事,本质上是在停机时间、数据安全、改造成本三者之间找平衡。没有完美的方案,只有当前条件下最合适的选择。

我的经验是:迁移前多花一天评估,上线后能少踩三天的坑。尤其是字符集检查和索引重建这两件事,90%的团队在规划阶段会觉得"到时候再说",结果上线后问题全部出在这两个地方。另外,选型阶段一定要让开发和DBA一起参与,而不是只听数据库厂商的售前演示------演示环境永远跑得比生产环境顺畅。

迁移完成后,把这次迁移的完整过程文档化,包括遇到的问题、踩的坑、最终方案。这个文档往往比迁移本身更有价值,因为下一个接手的人大概率要面对同样的场景,而他能参考的最好材料就是你这次留下的真实记录。

相关推荐
晴天¥1 小时前
Oracle 19c RAC修改监听默认端口
数据库·oracle
皮卡祺q2 小时前
【redis1】基本指令,五大数据类型,存储优化,使用场景】
数据库·redis·缓存
杜子不疼.2 小时前
Agent Skills 的演进治理与 Swarm Skills 自演进
服务器·数据库·microsoft
wanghowie2 小时前
26.v3 核心升级:语义层 + 指标体系——禁止 LLM 直连 SQL
数据库·sql
袋鼠云数栈2 小时前
数栈 V7.0 多模态数据智能平台:打造 AI-Ready 的企业数据底座
大数据·数据结构·数据库·人工智能·数据治理·多模态
Mr. zhihao2 小时前
Redis Bitmap:BitCount、bitTop的使用业务场景
数据库·redis·缓存
永远不会出bug2 小时前
PgSql数据库函数
数据库
Volunteer Technology2 小时前
Flink Sink
大数据·数据库·flink
程思扬2 小时前
Android Room 数据库跨版本升级闪退问题根治方案
android·数据库·oracle