干这行的人,早晚会撞上「mysqldump 数据库迁移」这道坎。不是说你非用它不可,而是当你半夜两点接到电话,说生产库要换机器、机房要搬迁、或者云厂商又搞什么幺蛾子的时候,你脑子里第一个蹦出来的、最不依赖外部工具、最不会因为网络权限卡壳的命令,就是它。mysqldump就是这么个老伙计,长得朴素,参数一堆,用好了是手术刀,用不好就是给自己挖坑的铁锹。我见过太多人,敲完一行命令就去喝咖啡,回来发现数据没导全,或者导进去之后字符集全乱成天书。今天不聊那些花里胡哨的GUI工具,就纯聊mysqldump这趟浑水怎么趟过去。

先说最扎心的一个认知:mysqldump不是备份工具,它是个逻辑导出工具。很多人把它当成了保险箱,以为每天cron跑一下,数据就万无一失了。可你仔细想想,它导出的是什么?是一大堆INSERT语句和建表语句的文本。这意味着什么?意味着你恢复的时候,要一条条执行这些语句,要重新构建索引,要重新计算统计信息。如果你的库有几百G,mysqldump导出的SQL文件可能有几十个G,恢复时间是以小时计的。这还只是时间问题,更麻烦的是,如果你在导出过程中,数据库还在持续写入,那么导出的数据可能是不一致的——除非你用了--single-transaction,而且还得是InnoDB引擎。所以,做迁移之前,先搞清楚你的业务能不能接受短暂停写,或者你能不能接受用binlog来补差量。这决定了你后面是轻松还是煎熬。
说到--single-transaction,这真是mysqldump里最值钱的一个参数,没有之一。它利用InnoDB的MVCC机制,开启一个可重复读的事务,让导出的数据快照保持一致。但是,这里有个大坑:如果你表里混着MyISAM引擎的表,或者你压根没注意引擎类型,那么--single-transaction对MyISAM是不生效的,它会退化成LOCK TABLES,把整张表锁死,然后导完再释放。你要是白天业务高峰期跑这个命令,那用户操作直接卡成PPT。所以,迁移前先查一遍表引擎,SELECT TABLENAME, ENGINE FROM informationschema.TABLES WHERE TABLESCHEMA='你的库名',把非InnoDB的表单独拎出来处理。另外,--single-transaction配合--quick和--skip-lock-tables,才能避免把结果集全缓冲在内存里,否则大表导着导着内存就爆了,进程直接被杀掉。
再往下走,就是字符集这个老生常谈却总是翻车的点。你导出的库是utf8mb4,目标库默认是latin1,导的时候不加--default-character-set=utf8mb4,导出来的中文全变成问号。更隐蔽的是,如果你用mysqldump导出的SQL文件,在导入时用source命令执行,终端客户端的字符集也得对得上。我的习惯是,导出时明确指定--default-character-set=utf8mb4,导入前先SET NAMES utf8mb4,然后在导入命令里也加上同样的参数。这样虽然啰嗦,但能省掉后续一堆排查乱码的时间。还有个小细节,导出文件里会带有SET @savedcsclient = @@charactersetclient这样的语句,你最好别动它,它是为了保证导入时能还原当时的字符集环境,如果你手动改了,反而容易出问题。
另一个容易被忽略的是权限问题。mysqldump需要什么权限?至少需要SELECT、SHOW VIEW、TRIGGER,如果你要导出存储过程和函数,还需要PROCESS权限——因为要读取c表。如果你用--single-transaction,还需要RELOAD权限来执行FLUSH TABLES WITH READ LOCK。很多人用root导,那当然没问题,但生产环境讲究最小权限,你给应用账号只开了SELECT权限,导到一半报错说TRIGGER没权限,那就尴尬了。所以迁移前,先测试一下你的导出账号,用mysqldump --all-databases --single-transaction --routines --triggers 这种全量参数跑一遍,看会不会报权限错误。别等到半夜割接的时候才第一次试,那纯粹是给自己上刑。
说完了导出,再聊导入。导入这事儿,最蠢的做法就是mysql < dump.sql,然后干等。你要是对恢复时间有要求,得先调整目标库的参数。导入前把binlog关掉,SET SQLLOGBIN=0,省得导入操作再写一遍binlog,白白浪费IO和磁盘空间。然后把uniquechecks和foreignkeychecks都设为0,导入完再恢复,这样能避免外键检查带来的额外开销。还有,如果你导入的是大库,建议把bulkinsertbuffersize和innodbbufferpoolsize调大一点,前者针对MyISAM,后者针对InnoDB。另外,导入过程别用一条INSERT带几百条VALUES的语句,那种超大SQL执行起来反而慢,不如拆成中等大小的批量插入。导入完一定要跑一遍ANALYZE TABLE,让优化器重新统计索引分布,否则你后面查询可能走错执行计划,慢得你怀疑人生。
还有一个很多人不知道的细节,mysqldump导出的文件里,默认包含了CREATE DATABASE和USE语句。如果你只是想把数据导入到一个已经存在的库里,千万别直接source整个文件,否则它会尝试创建同名库,然后切过去,把数据导到你不想导的地方。解决方案是导出时用--no-create-db参数,或者导入前用sed把这两行删掉。更稳妥的做法是,导出时只导出表结构和数据,用--no-create-db,然后导入时明确指定目标库:mysql -u用户 -p目标库 < dump.sql。这样逻辑清晰,不容易出幺蛾子。另外,如果你要迁移的只是部分表,用--tables参数指定表名,后面跟的库名就失效了,这个坑也踩过不少次,记一下。
说一个实战中特别有用的小技巧:用mysqldump做迁移,别一次性导全部库。拆开导,按业务模块分文件,这样出问题的时候能定位到具体是哪个库哪张表,恢复的时候也能只回滚那部分。而且,拆开导能并行,比如你分四个文件,同时开四个导入进程,速度能快不少。当然,并行导入要注意表之间的外键依赖,最好先导主表,再导关联表。还有,导完一个库,立刻验证一下行数,跟源库对比SELECT COUNT(*),别等全部导完了再验证,那时候发现问题,排查范围就大了。用mysqldump做迁移,本质上是一场对细节的敬畏。它不聪明,但足够可靠,只要你把每个参数、每个前置条件都照顾到,它就能稳稳当当把数据从A搬到B。怕就怕你把它当黑盒,敲完命令就撒手不管。数据迁移这事儿,从来都是过程越谨慎,结果越平淡,而平淡,才是最好的结局。


