干过运维的都知道,数据库迁移这事儿,看着简单,真上手全是坑。尤其是MySQL,平时跑得好好的,一到搬家就各种幺蛾子:权限丢了、字符集乱了、数据对不上数。我见过太多人直接拷贝data目录,结果启动报错,一脸懵地跑来问。今儿就把这些坑一个一个填平,让你把MySQL文件从一个机器挪到另一个机器,跟搬个箱子一样踏实。

先说最直接、也最容易被误解的一条:千万别图省事直接复制整个data目录。你可能会想,MySQL不就是把数据存文件里吗?我整个文件夹拷过去不就完了?理论上没错,但实际执行时,你拷过去的文件里藏着各种系统相关的路径信息、权限标识、甚至还有正在写入的redo log。目标机器的目录结构、用户权限、MySQL版本但凡有一点差异,启动时轻则报错,重则直接崩库。而且,你要是赶在业务高峰直接拷,拷到一半数据还在写,拷出来的文件本身就是个残缺品。
那正确姿势是啥?分两种情况:一种是停机迁移,一种是热迁移。停机迁移最靠谱,也最推荐新手用。流程很简单:先把MySQL服务干净地停掉,别用kill -9硬杀,要用mysqladmin shutdown或者service mysql stop这种优雅的方式,让InnoDB的缓冲池把脏页刷干净。然后,你再去拷贝data目录,这时候拷出来的文件就是一致性的快照。拷完到新机器上,确保目录归属是mysql用户,权限是750或者700,然后直接启动,基本一次过。
但很多场景下不允许停机,比如线上商城,你总不能跟老板说“我要搬家,网站关两小时”。这时候就得用热迁移方案。热迁移的第一选择是mysqldump逻辑备份,它把数据导出成SQL文件,相当于把数据库“翻译”成纯文本。命令很简单:mysqldump -u root -p --single-transaction --routines --triggers --events 数据库名 > 备份.sql。这里有个关键参数--single-transaction,它让InnoDB引擎在备份时开启一个一致性快照,不影响线上读写。恢复的时候,mysql -u root -p 数据库名 < 备份.sql,一条命令搞定。
不过mysqldump有个毛病:数据量大时特别慢,几百万行的表导出来就是几个G的SQL文件,光传输和导入就够你喝一壶的。这时候就该上物理热备份工具了,比如Percona XtraBackup或者MySQL Enterprise Backup。XtraBackup的玩法是,它直接复制数据文件,但复制过程中业务照常写,它通过复制redo log来保持一致性。命令大概是:xtrabackup --target-dir=/backup/mysql,然后--prepare阶段把日志应用进去,再拷贝到新机器。这套流程比mysqldump快一个数量级,而且恢复到新库后,数据一致性有保证。
再聊一个容易忽略的细节:字符集和排序规则。很多人迁移完发现中文乱码,第一反应是程序问题,其实十有八九是库表的默认字符集没对齐。你在旧库建表时用了utf8mb4,新库默认还是latin1,那导入的汉字全变问号。解决的办法是,导出时用--default-character-set=utf8mb4指定,导入时同样指定。更保险的做法是,在恢复前先检查目标库的全局变量:SET NAMES utf8mb4;,再执行导入脚本。另外,建表语句里的ENGINE=InnoDB和AUTOINCREMENT这种属性,也要确认目标MySQL版本支持,别旧库5.6的语法拿到8.0上跑,报错报得你怀疑人生。
权限和账号信息是迁移里最容易被忽略的雷区。你光迁移数据文件,mysql.user表里的账号密码和权限信息也得跟着走。用mysqldump导出时,记得加上--all-databases,这样会把mysql系统库也导出来,账号授权全在里面。但有个坑:MySQL 8.0的密码插件是cachingsha2password,老版本是mysqlnative_password,你要是从5.7迁到8.0,直接导入账号,某些老客户端可能连不上。这时候要么在迁移后手动升级密码插件,要么在导出时用--ignore-table=mysql.user,然后在目标库手动重建账号。我倾向后者,因为重建账号时顺便能清理掉一堆僵尸账号,安全又干净。
还有个细节很多人栽过跟头:表结构里的视图、存储过程、触发器。这些对象在mysqldump默认导出里是不带的,除非你加了--routines和--triggers参数。我见过有人迁移完,数据全在,但前端报表全挂了,一查是视图没了。所以导出时务必把这三个参数全加上。另外,如果要迁移的是分库分表的环境,比如用了MyCAT或ShardingSphere,那情况更复杂,因为元数据在中间件里,MySQL文件里只有分片后的物理表。这种场景下,你得先导中间件的配置,再逐个分片用XtraBackup做物理备份,统一恢复到目标集群。
说下迁移完的验证流程,这一步千万别省。启动新库后,先跑一下mysqlcheck -u root -p --auto-repair --check 数据库名,检查表有没有损坏。然后随机抽几张核心表,对比行数:SELECT COUNT() FROM 旧库.表名; 和 SELECT COUNT() FROM 新库.表名; 数字对不上,说明迁移过程丢了数据,赶紧排查。更严谨的做法是,用pt-table-checksum这个工具,它能全量比对每个表的每一行,校验CRC值,比数行数靠谱得多。校验通过后,再把应用程序的数据库连接串改到新地址,跑一轮冒烟测试,确认写入、读取、事务都正常,这才算真正迁移完成。
其实迁移这件事,说难也难,说简单也简单。难的是各种边界情况,比如超大表、跨版本升级、主从同步状态。简单的是,只要你遵循一条原则:先备份,再操作,验证,基本不会出大岔子。我见过太多人迁移完直接删旧库,结果新库有问题,哭都来不及。所以务必保留旧库至少一周,等新库稳定运行了,再清理。
回到标题说的“无缝切换”,真正的无缝不是靠运气,而是靠预案。你在迁之前就要想好:如果新库起不来,怎么回滚?如果数据对不上,是重导还是增量补?这些都想清楚了,迁移就成了一件按部就班的体力活。工具就那些,命令也就那几条,关键是流程要严谨。下次再让你迁移MySQL,别慌,按这个路子走,稳得一批。


