首页 / 资讯中心 / 文章详情

MySQL单库逻辑迁移SOP:mysqldump+scp改名导入与权限处理

MySQL单库逻辑迁移SOP:mysqldump+scp改名导入与权限处理 ★ FEATURED ARTICLE
前几天刚配合业务做了一次单库逻辑迁移源实例在某个IDC机房目标实例在另一个区域而且业务要求把库名一起换掉。整个任务拆下来就是“mysqldump导出、scp传输、改名导入、权限处理”这条线虽然说每个环节都是基础操作真串起来还是有不少坑。趁着这次刚做完我把这套MySQL单库逻辑迁移SOP完整记录一下尤其是改名导入和权限迁移这两个容易被忽视的环节后面再有类似需求可以直接照着抄作业。这套方案适用于中小型单库、可以接受一定停机窗口或者只需要把数据搬到新实例的场景。如果你要迁移的是几百G甚至上T的库那不建议走逻辑导出物理备份或者云上迁移工具更合适。但如果只是在实例之间倒库、跨版本导数据、顺手改个库名那么mysqldump加scp依然是最简单、最透明、最不依赖商业工具的做法。1. 迁移场景与整体设计思路1.1 什么场景需要单库逻辑迁移这次的需求背景很简单某业务系统的订单库biz_order_v1一直跑在一台混部的MySQL实例上磁盘使用率已经到85%而且该实例上还有其他团队的库彼此之间资源互相影响。业务方希望把订单库单独迁到一台新实例同时趁这次迁移把库名改成biz_order_v2应用侧统一切换到新库名。这种“单库迁移 改名 权限重建”组合在实际运维中非常常见。区别于整实例迁移单库逻辑迁移的好处在于只搬必要的业务数据不需要把实例上无关的其他库也带走可以跨MySQL小版本比如从5.7迁到8.0逻辑导出文件相对更容易兼容可以在迁移过程中顺手调整库名、表名、字符集等元数据对源实例的影响范围可控迁移失败也容易回退。当然逻辑迁移也有明显短板速度慢、dump文件大、导入时对目标库的IO压力高。所以它适合“业务可停写一段时间”的场景或者你愿意配合增量同步工具做切换。下面这套SOP默认是在可停写的维护窗口内执行的。1.2 迁移方案对比为什么选mysqldump加scp真正做数据库迁移可选方案不少常见的有物理文件复制、基于备份恢复、逻辑导出导入、在线DTS工具等。针对这次“单库、中型数据量、要改名”的需求我最终选了mysqldump导出加scp传输的方案核心考虑是方案优点缺点适用场景物理文件拷贝速度快几乎无逻辑转换版本强相关无法改库名操作繁琐同版本、同平台大库迁移基于备份恢复对源库影响小可细粒度恢复需要额外的备份系统恢复目标库名不够灵活有完善备份体系的公司逻辑导出导入兼容性好对象可控可改库名速度慢dump文件大导入耗时中小库、跨版本、改造场景在线DTS工具不停业务自动同步依赖中间件或云平台部分场景收费需要持续增量同步的大型系统逻辑导出虽然听起来“原始”但它是唯一能让我完全掌控每个步骤的方案。mysqldump生成的SQL文件是文本我可以检查、替换、拆分、压缩、比对出任何问题都能看到原始数据。scp则是最直接的传输方式配合SSH密钥和md5校验文件安全性和完整性都看得见。1.3 整个SOP的流程地图这次迁移我按下面这条线推进每个环节都有独立的检查和回退点源库摸底确认库大小、表数量、对象数量、字符集、账号依赖。mysqldump导出生成一致性快照保留存储过程、函数、触发器、事件。dump文件自检确认包含建库语句、USE语句、表结构、关键数据。scp传输到目标机保留文件时间戳传完做md5比对。改名处理把dump文件里的旧库名批量替换成新库名。目标库导入通过mysql客户端执行导入记录日志。权限处理把源库相关账号迁移到目标实例并修改授权库名。迁移后验证比对对象数量、数据量、自增ID、业务连通性。切换与回退所有验证通过后让应用切换到新库同时保留源库只读状态一段时间。每个环节都要有产出物和检查点后面我会逐个详细说明。2. 迁移前准备摸底排查与风险预估2.1 源库信息采集迁移前最忌讳拎起命令就导很多问题都是因为对源库不熟悉导致。我先在源实例上采集了一轮基本信息主要包括库的总大小按表拆分的占用空间表数量、最大表、行数估算是否有视图、触发器、存储过程、函数、事件默认字符集和排序规则自增列当前值、外键关系是否有需要特殊处理的特殊数据类型比如JSON、GEOMETRY、二进制字段。采集库大小时直接查information_schema最方便SELECT table_schema AS db_name, ROUND(SUM(data_length index_length) / 1024 / 1024, 2) AS total_mb, COUNT(*) AS table_count FROM information_schema.tables WHERE table_schema biz_order_v1 GROUP BY table_schema;再按表粒度看一眼大表分布SELECT table_name, ROUND((data_length index_length) / 1024 / 1024, 2) AS size_mb, table_rows FROM information_schema.tables WHERE table_schema biz_order_v1 ORDER BY size_mb DESC LIMIT 20;注意table_rows是估算值尤其是InnoDB表不精确。真要对比数据量后面导入完成后还得用COUNT(*)抽样核对。另外字符集信息要单独看避免导出导入后出现乱码SELECT default_character_set_name, default_collation_name FROM information_schema.schemata WHERE schema_name biz_order_v1;2.2 权限与账号盘点这是一个经常被忽略、却是迁移后第一个炸雷的环节。mysqldump只导出库对象不导出MySQL实例层面的用户和授权。也就是说源库的业务账号app_user即使只访问biz_order_v1也不会跟着dump文件跑到目标实例。所以我在导出前就先做了账号盘点把源实例上所有可能涉及目标库的账号都列出来SELECT user, host, account_locked, password_expired FROM mysql.user WHERE user NOT IN (mysql.session, mysql.sys, mysql.infoschema, root) ORDER BY user, host;然后逐个账号查看权限重点关注哪些账号拥有biz_order_v1库的权限SHOW GRANTS FOR app_user%;如果账号很多手工敲不现实可以用下面的批量查询生成所有授权语句然后保存成SQL脚本SELECT CONCAT(SHOW GRANTS FOR , user, , host, ;) FROM mysql.user WHERE user NOT IN (mysql.session, mysql.sys, mysql.infoschema, root);拿到输出后循环执行这些SHOW GRANTS把结果收集到授权脚本里。后面迁移到目标实例时只需要替换授权语句中的旧库名即可。2.3 空间、网络、窗口评估迁移过程中有三个资源瓶颈源库所在机器磁盘、目标库所在机器磁盘、源和目标之间的网络带宽。先算dump文件大小。mysqldump出来的SQL文件通常是库实际数据量的1.2到1.5倍如果再用gzip压缩体积能降到原来的20%到40%。所以源端和目标端至少要预留数据量1.5倍以上的临时磁盘空间。我这次库数据量大约60Gdump文件接近80Ggzip压缩后只有22G磁盘压力反而主要来自解压和导入过程的临时空间。再看传输带宽。我们源和目标之间是专线但带宽只有50Mbps22G压缩文件传过去大概需要一个多小时如果传输中断再重来时间成本很高。所以我在传输前把文件拆分或者直接上rsync方案后面第四部分会细说。最后是和业务确认停机窗口。mysqldump的--single-transaction虽然能保证导出期间源库可写但导出完成后源库的数据如果继续变化dump文件和源库就不一致了。所以严格来说这套逻辑迁移适合“维护窗口内停写”或者你把导出当作全量基线之后再配合增量binlog同步。本次迁移业务侧给了4小时维护窗口我评估后认为来得及才决定用这个方案。3. mysqldump导出关键参数与一致性保证3.1 常用导出命令模板mysqldump的参数非常多但不是每个都需要。我这次用的命令是这样mysqldump \ -h 10.0.0.10 \ -u dba_backup \ -p \ --single-transaction \ --set-gtid-purgedOFF \ --routines \ --triggers \ --events \ --databases biz_order_v1 \ --default-character-setutf8mb4 \ --hex-blob \ --max-allowed-packet512M \ --result-file/data/dump/biz_order_v1.sql如果你怕密码在命令行里暴露就不要在命令行直接写密码让mysqldump交互式提示输入。使用--result-file而不是直接重定向目的主要是避免编码转换问题在Linux上影响不大但养成习惯没坏处。导出过程中我用screeen或nohup挂起避免SSH断线导致导出中断nohup mysqldump ... --result-file/data/dump/biz_order_v1.sql /data/dump/export.log 21 3.2 参数背后的含义与取舍这里挑几个关键参数说一下很多人只是照着文档抄不知道为什么要加。--single-transaction是InnoDB表导出时最重要的参数。它会在开始导出时开启一个一致性快照导出过程中源库仍然可以正常写入你的dump文件内容是快照时刻的数据不会因为中途有事务提交而前后不一致。代价是如果库里还有MyISAM表这个参数对它们无效仍然会锁表。所以在导出前要确认库内是否全部是InnoDB表如果有MyISAM最好提前转成InnoDB或者接受锁表风险。--set-gtid-purgedOFF是MySQL 5.6开启GTID之后不能忽略的参数。如果源实例开启了GTID不带这个参数导出会在dump文件头部写入SET GLOBAL.GTID_PURGED...这样的语句。在目标实例上执行时如果目标实例已经配置了GTID这条语句很容易报错导致导入中断。我们这次是普通主从架构不依赖GTID所以直接设为OFF让文件更干净。--routines --triggers --events是导出存储过程、函数、触发器、事件。默认情况下mysqldump只导出表和视图如果你库里有存储过程不带这个参数会丢对象。需要注意导出的存储过程和触发器会带有DEFINER信息后面目标实例上如果账号名不一致导入会报ERROR 1449这是第7部分要讲的问题。--hex-blob会把二进制字段以十六进制形式导出避免导入时因为字符集或特殊字符导致数据损坏。这在大字段迁移时很有用。--max-allowed-packet512M调整的是mysqldump发送SQL语句的最大包大小。如果库里有超大字段或者超长的INSERT语句默认配置太小会报mysqldump: Error 2020: Got packet bigger than max_allowed_packet错误。这个参数只影响导出阶段导入阶段还要在mysql客户端设置同样的参数。3.3 导出过程中的性能控制mysqldump默认是单线程导出速度不算快但胜在稳定。我在导出时观察过源库IO没有被打满CPU占用也比较低主要瓶颈是磁盘读和网络出口。如果你的库比较大可以用并行导出的思路把按照表拆分多个dump文件分别导出例如mysqldump ... biz_order_v1 order_table_1 --result-fileorder_table_1.sql mysqldump ... biz_order_v1 order_table_2 --result-fileorder_table_2.sql但这里有个问题多个mysqldump进程同时跑--single-transaction分别建立快照虽然每张表内部是一致的但表与表之间不一定来自同一个时间点这对于有外键关系的库是隐患。所以我更倾向于单进程整体导出除非表之间完全没有关联或者你有明确的时间点对齐手段。导入端的性能控制更重要。导入时是大量INSERT语句速度受限于目标实例的磁盘IO和binlog写入。如果目标实例开启了binlog导入期间会产生非常大的binlog文件建议把sync_binlog和innodb_flush_log_at_trx_commit临时调整一下或者干脆导入完成后马上清理旧binlog。但生产环境不建议轻易改这些参数操作前先确认维护策略。3.4 导出产物自检导出完成后不要急着传输。先对dump文件做一轮自检确认内容符合预期# 文件大小 ls -lh /data/dump/biz_order_v1.sql # 头部和尾部 head -n 30 /data/dump/biz_order_v1.sql tail -n 5 /data/dump/biz_order_v1.sql # 检查关键语句 grep -n CREATE DATABASE /data/dump/biz_order_v1.sql grep -n ^USE /data/dump/biz_order_v1.sql grep -c CREATE TABLE /data/dump/biz_order_v1.sql grep -c INSERT INTO /data/dump/biz_order_v1.sql重点确认文件里有没有建库语句和USE语句。因为这次迁移要改库名我特意用了--databases biz_order_v1参数这样dump文件里会包含CREATE DATABASE /*!32312 IF NOT EXISTS*/ biz_order_v1 /*!40100 DEFAULT CHARACTER SET utf8mb4 */; USE biz_order_v1;这两行就是后面改名替换的关键目标。如果导出时没有加--databasesdump文件不含建库语句和USE语句导入前需要手动在目标实例建库并且设置默认schema。对于“要改成新库名”的场景我更推荐带库名导出因为替换逻辑会更明显。4. scp传输文件搬运与完整性校验4.1 scp命令与免密配置导出文件确认没问题后就要传到目标实例所在机器。基础命令很简单scp -C -P 22 /data/dump/biz_order_v1.sql dba10.0.0.20:/data/dump/-C是开启SSH压缩传输SQL文本文件时效果明显但如果你已经把文件gzip压缩过就不建议再用-C白白消耗CPU。建议导出后先压缩gzip /data/dump/biz_order_v1.sql压缩后文件变成biz_order_v1.sql.gz然后scp传输scp /data/dump/biz_order_v1.sql.gz dba10.0.0.20:/data/dump/我这次22G的压缩文件在50Mbps带宽下大约传了50多分钟主要是小包和TCP窗口的问题速度上不去。如果你有更高带宽或者文件更大可以考虑用rsync或者mbuffer加速。scp默认会用交互式密码验证为了不影响SOP的自动化我提前配置了源机和目标机之间的SSH密钥认证。在两台机器上分别生成密钥并互相拷贝公钥之后scp就不再需要密码。4.2 传输中断续传方案scp有个很让人头疼的问题不支持断点续传。传输到一半断了整文件重来。为了规避这个问题有两种思路第一种是老老实实把源文件拆分成多个较小的分片分别scp传完后在目标端合并。拆分可以用splitsplit -b 2G /data/dump/biz_order_v1.sql.gz /data/dump/biz_order_v1.part_生成一串biz_order_v1.part_aa、biz_order_v1.part_ab这样的分片然后并行传输目标端再合并cat /data/dump/biz_order_v1.part_* /data/dump/biz_order_v1.sql.gz这个方法能减少单次传输失败造成的损失但引入了额外的合并操作和校验复杂度。第二种是改用rsync它天然支持断点续传rsync -avP --partial /data/dump/biz_order_v1.sql.gz dba10.0.0.20:/data/dump/--partial会在传输中断时保留已传部分下次再跑可以续传。这次我实际操作时因为现场要求统一走scp流程所以用了split分片方案同时并行跑多个scp进程。如果让我个人选局域网内文件不大直接用scp就好大文件还是优先rsync。4.3 校验方法和异地落盘检查传输完成后第一件事不是解压导入而是校验文件完整性。网络上传输也可能出现静默损坏尤其经过多次路由设备时虽然概率很低但数据库文件一旦损坏导入时容易报错且很难排查。先在源端计算MD5md5sum /data/dump/biz_order_v1.sql.gz然后在目标端计算同样文件的MD5两个值必须一致md5sum /data/dump/biz_order_v1.sql.gz如果不一致说明文件损坏不要抱着侥幸心理继续做直接重新传。更严格一点还可以做sha256sum防碰撞性更好但md5已经能满足常规校验。在目标端解压之前还需要检查gzip文件本身完整性gzip -t /data/dump/biz_order_v1.sql.gz这个命令会读取整个压缩文件检测是否有CRC错误。之前有一次传输没报错但gzip -t发现了文件尾部被截断就是因为ssh会话异常中断导致文件不完整如果没有做这一步后面导入可能进行到一半才报错。5. 改名导入从dump文件恢复到目标库5.1 准备目标实例与库名迁移前需要先确认目标实例状态。我在目标实例上检查了磁盘空间、InnoDB buffer pool、binlog配置、字符集等基本项。然后提前创建目标库CREATE DATABASE IF NOT EXISTS biz_order_v2 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;为什么先手动建库因为目标库名和源库名不同虽然替换后的dump文件里也有CREATE DATABASE IF NOT EXISTS但为了控制字符集和排序规则显式创建更稳妥。如果你不关注这些手动建库也可以在导入时跳过冲突。另外目标实例的sql_mode也需要注意。MySQL 8.0默认sql_mode包含ONLY_FULL_GROUP_BY、STRICT_TRANS_TABLES等如果源库的SQL行为是按老版本习惯写的导入时可能触发严格模式报错。稳妥的做法是先查看源实例的sql_mode在目标实例设置保持一致SELECT sql_mode; SET GLOBAL sql_mode ...;这个设置不一定长期保留但导入期间保持一致能减少意外。5.2 改名导入的两种方式改名导入是这次迁移最核心的一步。源库名biz_order_v1要变成biz_order_v2最简单的思路是对dump文件做替换。第一种方式把CREATE DATABASE和USE两行替换成目标库名sed -i s/biz_order_v1/biz_order_v2/g /data/dump/biz_order_v1.sql这个命令会把文件中所有反引号包裹的biz_order_v1都替换成biz_order_v2。它不仅能处理建库和USE语句还能处理视图定义、存储过程、触发器里出现的biz_order_v1.table_name这种全限定名。但如果某个字段名或注释里恰好也出现了相同字符串就可能被误替换。所以在替换前先统计一下出现位置grep -n biz_order_v1 /data/dump/biz_order_v1.sql | head -50人工看一眼这些位置是不是都和库名相关。我这次检查下来所有出现都是库名或全限定对象名替换是安全的。如果用我前面说的方法导出时加了--databases那么文件里至少会有两条关键记录CREATE DATABASE /*!32312 IF NOT EXISTS*/ biz_order_v2 ...; USE biz_order_v2;替换后导入时目标库会被再次确保存在并且连接进去执行后续建表语句。第二种方式如果不想大面积替换也可以在导入时先进入目标库再执行dump但这种方式只适用于导出文件没带--databases的情况而且视图和触发器的定义里如果包含旧库名还是需要额外处理。所以我推荐第一种方式。5.3 导入执行与日志观察改名替换完成后开始导入。导入命令很简单但必须记录日志mysql -h 10.0.0.20 -u root -p \ --default-character-setutf8mb4 \ --max-allowed-packet512M \ /data/dump/biz_order_v1.sql /data/dump/import.log 21如果dump文件是gzip压缩的先在目标端解压或者用zcat直接导入zcat /data/dump/biz_order_v1.sql.gz | mysql -h 10.0.0.20 -u root -p --default-character-setutf8mb4 --max-allowed-packet512M /data/dump/import.log 21导入过程是单线程执行速度取决于目标实例IO。我在导入时观察到前30分钟主要在建表和插入初期数据后面大表插入阶段磁盘IO持续走高。如果在执行过程中发现ERROR不要直接忽略先停下来定位问题。常见的错误我放在第7部分统一说。导入完成后检查日志尾部有没有报错tail -n 30 /data/dump/import.log grep -n ERROR /data/dump/import.log | head -50注意有些错误信息后面会继续执行所以建议用grep把所有ERROR全部过一遍而不是只看最后几行。5.4 导入后的对象核对导入完成不代表迁移完成。我先做了一轮对象层面的核对确认目标库里的表和源库一致。源库直接查SELECT table_name, table_type FROM information_schema.tables WHERE table_schema biz_order_v1 ORDER BY table_name;目标库同样查SELECT table_name, table_type FROM information_schema.tables WHERE table_schema biz_order_v2 ORDER BY table_name;将两份结果做diff确认表、视图数量一致。触发器、存储过程、函数、事件分别查询SELECT trigger_name FROM information_schema.triggers WHERE trigger_schema biz_order_v1; SELECT routine_name FROM information_schema.routines WHERE routine_schema biz_order_v1; SELECT event_name FROM information_schema.events WHERE event_schema biz_order_v1;在目标库执行同样的查询只是把schema换成biz_order_v2对比结果。行数核对不能依赖table_rows我抽了三张大表分别执行SELECT COUNT(*) FROM biz_order_v1.order_main; SELECT COUNT(*) FROM biz_order_v2.order_main;结果必须完全一致。对于大表COUNT(*)很耗时可以选取自增ID的MAX(id)和MIN(id)来做快速对比但最终还是要抽样全表确认一次。6. 权限处理与迁移后验证6.1 用户创建与授权脚本生成这步是很多人在迁移后栽跟头的地方。目标实例上默认没有源实例的业务账号即使导入成功应用也连不上。我建议在切换前就把授权脚本准备好。先通过第2部分盘点的账号列表在目标实例上创建用户。可以用SHOW CREATE USER拿到源实例用户的完整信息比如密码哈希、插件、过期策略mysql -h 10.0.0.10 -u dba_backup -p -N -e SELECT CONCAT(SHOW CREATE USER , user, , host, ;) FROM mysql.user WHERE user NOT IN (mysql.session, mysql.sys, mysql.infoschema, root) | mysql -h 10.0.0.10 -u dba_backup -p把输出里的CREATE USER语句保存到目标实例执行然后在目标实例上给业务账号授权。授权语句要把库名替换成biz_order_v2CREATE USER IF NOT EXISTS app_user% IDENTIFIED BY 这里换成密码; GRANT SELECT, INSERT, UPDATE, DELETE ON biz_order_v2.* TO app_user%; GRANT EXECUTE ON biz_order_v2.* TO app_user%; FLUSH PRIVILEGES;如果源实例上有通过角色授权的MySQL 8.0还支持SHOW GRANTS输出带USING信息需要把角色也一并迁移。这里不展开但授权脚本里要留意。权限处理的核心是“能少给就少给”不要图省事直接GRANT ALL尤其生产环境。对迁移后的新库建议按业务实际使用的SQL类型只授权必要的权限。6.2 权限核对清单授权完成后我用下面这套核对清单确认权限和账号状态目标实例上是否存在所有需要迁移的账号每个账号的host是否符合来源IP段限制每个账号能否登录目标实例业务账号对biz_order_v2库是否有预期的表权限存储过程、函数、触发器的DEFINER账号是否已在目标实例存在是否有账号被意外锁定或密码过期。查询某个账号在目标库上的权限SHOW GRANTS FOR app_user%;同时执行一次真实登录测试mysql -h 10.0.0.20 -u app_user -p -e USE biz_order_v2; SHOW TABLES;如果登录报Access denied先检查账号host匹配、密码哈希是否兼容、plugin是否一致。MySQL 8.0默认使用caching_sha2_password如果应用驱动是老版本可能连接报错需要改成mysql_native_password或在应用侧升级驱动。6.3 业务连通性验证和回滚方案权限处理完成后我拉上业务方做了联合验证用业务配置的新JDBC连接串连接目标实例执行几个核心查询接口确认返回数据和源库一致跑一条事务型写入SQL确认INSERT/UPDATE正常确认存储过程和定时事件能正常执行确认监控、备份系统能识别新库。回滚方案在切换前就写好。我的思路是源库保持原样不删除、不改名只在切换期间设置只读或暂时停机。如果目标库验证失败让应用配置回切到源库影响时间控制在分钟级。如果切换后一段时间内业务正常再考虑清理源库或做归档。有一点要特别提醒如果切换窗口内源库还在写入数据就会持续变化目标库和源库不等价。所以要么在导出完成后立即切只读要么在业务低峰窗口执行并禁止写入。我是先让业务停写再导出导出完成后源库保持只读直到验证通过。7. 常见问题与排查技巧实录7.1 问题速查表这次迁移过程中和以往经验结合我整理了下面这张速查表常见报错/现象可能原因处理方式mysqldump报Couldnt execute SHOW CREATE TABLE账号权限不足使用具备PROCESS、RELOAD、SELECT权限的账号ERROR 2020: Got packet bigger than max_allowed_packetdump包大小超限mysqldump加--max-allowed-packet并同步修改mysql客户端参数导入时报ERROR 1449存储过程/触发器DEFINER账号在目标实例不存在先创建对应账号或修改DEFINER导入时报ERROR 1227或ER_SPECIFIC_ACCESS_DENIED_ERROR当前用户缺少SUPER或SET_USER_ID权限用高权限账号导入或拆分处理导入后中文乱码源、目标、会话字符集不一致统一使用utf8mb4检查连接字符集和表定义scp传输后MD5不一致网络传输损坏重新传输必要时使用rsyncsed替换后导入报错误替换了非库名字段重新导出使用精准替换检查grep结果目标库表数量少触发器/存储过程未导出mysqldump加--routines --triggers --events应用连接目标库慢账号插件不兼容或DNS解析问题检查caching_sha2_password升级驱动或改插件大表导入慢目标实例IO瓶颈、binlog写入量大临时调整IO参数导入时关闭或归并binlog表格只列了高频问题下面挑三个我这次实际踩到的坑详细说一下。7.2 几条独家经验第一个坑是DEFINER问题。源库某个存储过程的DEFINER是old_dba%但源实例上这个账号已经被删了。平时查询存储过程没问题因为MySQL只在执行时才检查DEFINER权限但导出导入时目标库建存储过程会校验DEFINER是否存在。解决办法是在目标实例先创建一个同名的DEFINER账号或者导入后执行ALTER DEFINER修改。最稳妥的是在迁移前就统一把所有对象的DEFINER改成迁移后仍然存在的账号。第二个坑是字符集不一致。源库表定义虽然是utf8mb4但mysqldump导出时如果客户端连接的默认字符集不是utf8mb4导出文件里的中文可能已经转成其他编码。解决方法是导出和导入都显式加--default-character-setutf8mb4并且在mysql客户端也带上相同参数。文本文件传输过程中不存在转码问题问题最常发生在缺少这个参数的地方。第三个坑是sed替换范围太大。我第一次用sed -i s/biz_order_v1/biz_order_v2/g全局替换结果把某张表的一个字段名也替换了。因为那个字段名就叫biz_order_v1_remark虽然不常见但确实发生了。后来我改成只替换反引号包裹的完整库名sed -i s/biz_order_v1/biz_order_v2/g /data/dump/biz_order_v1.sql这样字段名不受影响。但如果字段定义里没有反引号还是可能漏。建议替换前先grep确认替换后再grep确认没有biz_order_v1残留。顺便提一下导入时临时关闭binlog的做法。目标实例如果开了binlog导入几十GB数据会产生巨量binlog既慢又占空间。在维护窗口内可以先确认目标实例不是从库、没有下游依赖然后临时做SET SESSION sql_log_bin 0;但mysqldump导入是通过mysql客户端执行的SET sql_log_bin0只对当前会话生效所以可以把这条语句追加到导入文件开头sed -i 1i SET SESSION sql_log_bin0; /data/dump/biz_order_v1.sql导入完成后把这条语句去掉或者不影响源文件。这个操作要非常谨慎确认目标库不会参与复制再使用否则会造成从库数据不一致。最后再分享一个小技巧这次迁移完我保留了一份“迁移前后对象数量对比”的SQL模板以后每次做类似迁移都直接复用。模板里就是查询information_schema.tables、triggers、routines、events然后按库名统计数量用一个简单的Python脚本做diff。虽然做起来不麻烦但没有模板和有模板的差距还是很大的尤其是凌晨做维护窗口的时候越简单越不会出错。迁移数据库这事儿最怕的不是技术难而是细节漏。按照这套SOP一步步走即便中途遇到问题也能精确定位到是导出、传输、导入还是权限的问题。
阅读完成 · 觉得有帮助?
咨询建站