mysqldump 备份恢复失败的 8 类报错实测:ERROR 3546、1449、1419、1153、1412,附 RTO 分步记录模板
在 MySQL 8.0.46 测试实例(自造数据 250 万行、236 MB 备份)上把逻辑备份恢复会遇到的 8 类失败一一复现:GTID_PURGED 重叠 3546、DEFINER 用户不存在 1449、补建用户漏 TRIGGER 1142、没有 SUPER 导触发器 1419、没加 --routines 1305、max_allowed_packet 1153、latin1 导出中文变问号、--lock-tables 卡写入、备份中做 DDL 1412。同时把一次恢复按步骤计时:导入 27.6 秒占约 85%,调 redo、刷盘参数和按表并行在这台机器上都没有明显缩短。附复现脚本、只读恢复就绪度 SQL、RTO 记录模板和实测 CSV。
MySQL 8.0.46 实测(测试实例、自造数据):236 MB 的 mysqldump 备份恢复到空实例用 27.6 秒,整次演练约 33 秒,导入占约 85%;临时关 redo、放宽刷盘、调大 redo 容量、按表并行导入的结果在 26.9–30.8 秒之间,都没有明显缩短,瓶颈是最大的 orders 表单线程导入。更大的风险是“导完才发现用不了”:同一份备份在非空实例上第 24 行就报 ERROR 3546,一张表都没建;DEFINER 用户不存在时导入成功,但视图、触发器、存储过程都报 ERROR 1449;备份中有人 ALTER 了还没导到的表,mysqldump 报 Error 1412、退出码 3,文件停在半截。
适用对象
- 备份成功不等于能恢复:8 类失败里有 3 类(DEFINER、--routines、latin1)导入时 rc=0,问题要到用的时候才出现,所以演练必须包含对象校验和 CHECKSUM 比对。
- GTID 实例导出的文件第 24 行带 SET @@GLOBAL.GTID_PURGED,导回任何 gtid_executed 非空的实例都会报 ERROR 3546;同一台演练机做第二次演练也会撞上。
- 带 DEFINER 的对象:用户不存在报 1449,补建了用户漏 TRIGGER 报 1142;普通账号导触发器报 1419,只去掉 DEFINER 子句还是 1419,要临时开 log_bin_trust_function_creators(已弃用)或授予 SET_USER_ID。
- 在这台 8 核机器上,恢复耗时的大头是最大表的单线程导入;改刷盘参数、关 redo、按表并行的差别都在约 ±10% 内,RTO 要按自己最大的表实测,不能照搬参数优化后的经验值。
- 备份脚本必须检查 mysqldump 的退出码和文件末尾的 Dump completed:备份中途 ALTER 一张表,mysqldump 报 1412、退出码 3,文件停在 LOCK TABLES `notes` WRITE。
一次恢复的时间花在哪:导入占约 85%
把一次完整演练拆成步骤分别计时:备份 1.8 秒(236 MB),gzip -1 压缩 1.7 秒(47 MB),本机复制 0.3 秒,导入 27.6 秒,CHECKSUM TABLE 加 COUNT(*) 逐表比对 1.2 秒,ANALYZE TABLE 0.02 秒,合计约 33 秒。三张表的 CHECKSUM 和源库一致(orders 1697083939、payments 4118135830、notes 2691864942)。
没算进去的:跨机器传输(要按实际带宽重测)、准备目标实例(重置 GTID、建 DEFINER 用户,下面几节会看到不做会怎样)、应用切换和冒烟测试。写 RTO 时这三步往往比导入更难估,记录模板里我单独留了行。
| 步骤 | 做法 | 耗时 | 说明 |
|---|---|---|---|
| 备份 | mysqldump --single-transaction --routines --triggers --events | 1.8 s | 236 MB,第 24 行带 GTID_PURGED |
| 压缩 | gzip -1 | 1.7 s | 47 MB |
| 传输 | 本机 cp | 0.3 s | 只是下限 |
| 导入 | mysql rst_lab < full.sql | 27.6 s | 约占 85% |
| 校验 | CHECKSUM TABLE + COUNT(*) | 1.2 s | 三表一致 |
| 统计信息 | ANALYZE TABLE | 0.02 s | — |
| 合计 | — | 约 33 s | 不含准备实例和应用切换 |
同一份备份,5 种导入方式的耗时
默认(sync_binlog=1,flush=1)
27.6 s
flush=2 + sync_binlog=0
28.2 s
redo 容量 100M → 2G
27.6 s
关 redo(DISABLE INNODB REDO_LOG)
26.9 s
按表拆 3 个文件并行
30.8 s
MySQL 8.0.46,测试实例、自造数据,每种 1 次;第 1 轮的数字在同一量级。
调参数和并行为什么没变快:瓶颈是最大那张表
放宽刷盘(innodb_flush_log_at_trx_commit=2、sync_binlog=0)28.2 秒,和默认的 27.6 秒没有区别。mysqldump 默认每条 INSERT 合并很多行,提交次数本来就少,少刷几次盘省不了多少。
默认 redo 容量 100M 时,目标实例错误日志里一直刷 Threads are unable to reserve space in redo log … log_checkpointer lagging 的警告,我以为找到瓶颈了。8.0.30 以后可以在线 SET GLOBAL innodb_redo_log_capacity,调到 2G 后警告没了,时间还是 27.6 秒。再用 ALTER INSTANCE DISABLE INNODB REDO_LOG 关掉 redo(8.0.21+,崩溃后实例不可恢复,只能用在可以整个重建的实例上),26.9 秒,只快了约 2.5%。
按表拆成 3 个文件并行导入,30.8 秒,反而慢了一点:orders 一张表就要 30.8 秒,payments 4.1 秒、notes 0.03 秒早就导完了,总时长等于最大那张表。要真正缩短,得把大表本身按主键范围拆成多块并行导入(MySQL Shell 的 util.dumpSchemas / util.loadDump 就是这么做的),或者换物理备份;这两种本次没测,不下结论。
还有一个会让恢复变慢的写法:用 --set-gtid-purged=OFF 导出的文件里没有 SET @@SESSION.SQL_LOG_BIN=0(默认文件里有 3 处),导入时每一行都写进目标实例的 binlog,耗时 33.4 秒,比默认慢约 20%,还多出 181 MB binlog。
ERROR 3546:带 GTID_PURGED 的备份导回非空实例,一张表都没建
GTID 实例上 mysqldump 默认 --set-gtid-purged=AUTO,备份时先提示 A partial dump from a server that has GTIDs will by default include the GTIDs of all transactions…,文件第 24 行写入 SET @@GLOBAL.GTID_PURGED=/*!80000 '+'*/ '03c8e5d4-…:1-539'。
我把这个文件导回源实例的另一个库(模拟“恢复到原库旁边对比”),第 24 行直接报 ERROR 3546 (HY000): @@GLOBAL.GTID_PURGED cannot be changed: the added gtid set must not overlap with @@GLOBAL.GTID_EXECUTED,用时 17 ms,rst2_lab 里 0 张表。它在建表之前就失败了,这一点反而是好事,不会留下半套数据。
更容易踩的是第二次演练:同一台演练机,上一轮导入已经把这批 GTID 记进了 gtid_executed,不重置就再导一次,同样在第 24 行报 3546。改法二选一:演练机是一次性的,每次导入前 RESET MASTER(8.0.46 里这条命令仍可用,8.2 起改名 RESET BINARY LOGS AND GTIDS);或者只是要数据、不需要接 GTID 复制,备份时加 --set-gtid-purged=OFF,但要注意上一节说的 binlog 写入问题。
DEFINER:导入成功,用的时候报 1449;补用户漏权限报 1142;普通账号导入报 1419
源库的视图、触发器、存储过程 DEFINER 都是 app@%。恢复到新实例时,导入 rc=0,看起来一切正常;但目标实例上没有 app 用户,SELECT 视图、往 orders 插一行(触发器)、CALL 存储过程,三处都报 ERROR 1449 (HY000): The user specified as a definer ('app'@'%') does not exist。插入失败意味着恢复后的库根本接不了写流量。
我第一次补建用户时只给了 SELECT、INSERT、UPDATE、DELETE、EXECUTE,插入时又报 ERROR 1142 (42000): TRIGGER command denied to user 'app'@'%' for table 'orders':触发器以 DEFINER 身份执行,DEFINER 自己要有 TRIGGER 权限。回源库跑 SHOW GRANTS FOR 'app'@'%',照着补 GRANT TRIGGER 后,插入成功,触发器把空 remark 填成了 n/a,存储过程返回 4 笔、3693.20。教训:DEFINER 用户和权限要按源库 SHOW GRANTS 整套复制,不要凭印象补。
另一种情况是恢复账号本身权限不够。用一个只有 rst2_lab 库 ALL 权限、没有 SUPER 和 SET_USER_ID 的 restorer 账号导入,27.5 秒后在第 314 行(建触发器)报 ERROR 1419 (HY000): You do not have the SUPER privilege and binary logging is enabled,库里只建了 digits、notes、orders 三张表,payments、视图、触发器、存储过程都没有,是一个半成品。
我先试了网上最常见的办法:sed 去掉所有 DEFINER 子句再导,结果还是 1419,因为开着 binlog 时建触发器本身就要求这个权限,跟 DEFINER 写的是谁无关。能导完的两种办法:临时 SET GLOBAL log_bin_trust_function_creators=ON(8.0.46 返回 Warning 1287,提示这个变量已弃用),导完对象属主变成了 restorer@%;或者保留 DEFINER,给恢复账号 GRANT SET_USER_ID,导完触发器属主仍是 app@%。我更推荐后者,属主不变、业务不受影响;8.2 以后这个权限拆成 SET_ANY_DEFINER 和 ALLOW_NONEXISTENT_DEFINER。
| 情况 | 现象 | 修法 | 实测结果 |
|---|---|---|---|
| 目标实例没有 DEFINER 用户 | 导入 rc=0;视图、触发器、存储过程报 1449 | 按源库 SHOW GRANTS 建用户 | 见下一行 |
| 补建了用户,漏 TRIGGER | 插入报 1142 TRIGGER command denied | GRANT TRIGGER | 插入成功,触发器生效 |
| 恢复账号无 SUPER / SET_USER_ID | 第 314 行 1419,只建了 3 张表 | — | 半成品 |
| 去掉 DEFINER 子句 | 仍然 1419 | 不够 | rc=1,27.8 s |
| 去 DEFINER + log_bin_trust_function_creators=ON | Warning 1287 已弃用 | 可用 | rc=0,属主变成 restorer@% |
| 保留 DEFINER + GRANT SET_USER_ID | — | 推荐 | rc=0,属主仍是 app@% |
没导存储过程、包太大、字符集:两个 rc=0 的坑和一个半截表
F4:mysqldump 默认导触发器,但不导存储过程和事件。备份命令没写 --routines,文件里 PROCEDURE 出现 0 次,导入 rc=0,CALL sp_user_paid(42) 报 ERROR 1305 (42000): PROCEDURE rst2_lab.sp_user_paid does not exist。备份命令要显式写 --routines --events。
F5:一张附件表里有一行 8 MB 的 LONGBLOB,导出文件里这一行就是一条 8388648 字节的 INSERT。目标实例 max_allowed_packet=4M 时,第 40 行报 ERROR 1153 (08S01): Got a packet bigger than 'max_allowed_packet' bytes,3 行只剩前 2 行。把目标实例临时调到 64M 后导入成功,3 行的 LENGTH 和 MD5 与源库一致。另外加 --hex-blob 时文件从 10 MB 变成 19 MB,单行也会翻倍,调 max_allowed_packet 时要按导出方式算。
F6:用 --default-character-set=latin1 导出 utf8mb4 的 notes 表,导出和导入 rc 都是 0,行数 2000 = 2000,只看 COUNT(*) 会以为没问题。CHECKSUM 从 2691864942 变成 3871291563,“订单备注 905 café”变成了“???? 905 café”,2000 行全部带问号,emoji 也丢了。café 没坏,是因为 é 在 latin1 里有;中文和 emoji 没有,被替换成了 ?,这种损坏不可逆。所以校验要比 CHECKSUM,不能只比行数。
备份本身的两个坑:--lock-tables 卡住写入,备份中做 DDL 报 1412
F7:备份时有一个会话每 10 ms 往 orders 插一行。不加 --single-transaction(默认 --lock-tables),备份 1.8 秒,期间插入最长等了 1626 ms,平均 7.17 ms;加 --single-transaction,备份 1.7 秒,插入最长 38.8 ms,平均 1.90 ms。这里只有 236 MB,表大的时候写入会被锁住整个导出时长。InnoDB 表一律加 --single-transaction。
F8:--single-transaction 也有它的坑。备份开始 0.5 秒后,另一个会话对还没导到的 notes 表执行 ALTER TABLE … ENGINE=InnoDB(33 ms 就完成了),mysqldump 导到 notes 时报 mysqldump: Error 1412: Table definition has changed, please retry transaction when dumping table `notes` at row: 0,退出码 3。文件里 orders 和 payments 是完整的,notes 只有建表语句,最后一行停在 LOCK TABLES `notes` WRITE,没有结尾的 Dump completed。
如果备份脚本只看“文件生成了没有”,这份备份会被当作成功保留下来,直到要用的那天。备份脚本至少要检查 mysqldump 退出码、错误输出和文件末尾的 -- Dump completed 三样,备份窗口内不跑 DDL,包括 pt-online-schema-change 和 gh-ost 这类工具。
8 类恢复失败速查:报错、导入时能不能发现、怎么修
下载的 CSV 里有每一项的实测耗时和原始报错,可以直接当作团队的恢复 FAIL 手册。最右一列最重要:导入时发现不了的,必须靠恢复后的检查来发现。
| 编号 | 报错 / 现象 | 原因 | 修法 | 导入时能发现吗 |
|---|---|---|---|---|
| F1 | ERROR 3546 at line 24 | GTID_PURGED 和目标 gtid_executed 重叠 | 一次性实例 RESET MASTER,或 --set-gtid-purged=OFF | 能(rc=1,0 张表) |
| F2 | ERROR 1419 | 恢复账号没有 SUPER / SET_USER_ID,开着 binlog 建触发器 | GRANT SET_USER_ID | 能(rc=1,半成品) |
| F3 | ERROR 1449 / 1142 | DEFINER 用户不存在 / 缺 TRIGGER 权限 | 按源库 SHOW GRANTS 整套复制 | 不能(rc=0) |
| F4 | ERROR 1305 | 备份没加 --routines | 显式 --routines --events | 不能(rc=0) |
| F5 | ERROR 1153 | 单行超过目标 max_allowed_packet | 目标实例临时调大 | 能(rc=1,缺行) |
| F6 | 中文变 ???? | --default-character-set 不是 utf8mb4 | 全程 utf8mb4,恢复后比 CHECKSUM | 不能(rc=0,行数一致) |
| F7 | 写入卡 1.6 s | 没加 --single-transaction | InnoDB 加 --single-transaction | 备份时能看到 |
| F8 | Error 1412,rc=3,文件截断 | 备份中有 DDL | 查退出码和 Dump completed | 能(但脚本常不检查) |
在生产库上先跑只读就绪度 SQL:提前知道恢复时会撞上哪几类
实验脚本会删库、RESET MASTER、关 redo,只能在一次性的测试实例上跑。到生产库上,先跑这份只读恢复就绪度 SQL(8 段,只有 SELECT,已过站内只读守卫测试):实例参数(gtid_mode、log_bin、max_allowed_packet、log_bin_trust_function_creators、redo 容量、字符集);所有 DEFINER 及这个用户在本实例是否存在;每个库的存储过程、事件、触发器数量;非 utf8mb4 的文本列;大 LOB / JSON 列;非 InnoDB 表;各库大小和最大的 10 张表;没有主键的表。
在源测试实例上的结果:gtid_mode=ON、log_bin=1、max_allowed_packet 64 MB、trust_function_creators=0、redo 100 MB;DEFINER 都是 app@%,本实例存在;rst_lab 有 1 个存储过程、1 个触发器;最大的表 orders 约 199 万行、数据 212 MB、索引 144 MB。拿到这份结果,就能对照上面的速查表提前准备:目标实例要建哪些用户、恢复账号要什么权限、max_allowed_packet 至少多大、最大的表大概要导多久。
把演练结果填进 RTO 记录模板:模板第一部分是本次实测的示例行,后面是空白行,每一步记录开始结束时间、产物大小、校验值和遇到的报错码,最后一行填目标 RTO 和是否达标。
觉得有用?转给正在做这件事的同事或朋友
参考依据
以下来源用于确认市场趋势、政策背景和术语边界;具体落地方案仍以客户的数据范围、权限和交付目标为准。
常见问题
mysqldump 导入报 ERROR 3546 GTID_PURGED cannot be changed 怎么办?
备份文件第 24 行的 SET @@GLOBAL.GTID_PURGED 和目标实例已有 GTID 重叠。目标是一次性演练机就先 RESET MASTER;只要数据、不接复制就用 --set-gtid-purged=OFF 重新导出。本次在非空实例上 17 ms 就失败,0 张表被建。
恢复后报 ERROR 1449 The user specified as a definer does not exist?
视图、触发器、存储过程的 DEFINER 用户在新实例上不存在。导入时 rc=0,用的时候才报错。按源库 SHOW GRANTS 整套复制用户和权限;本次只补了基本权限、漏了 TRIGGER,插入又报 ERROR 1142。
ERROR 1419 You do not have the SUPER privilege and binary logging is enabled 怎么解决?
开着 binlog 时,没有 SUPER 或 SET_USER_ID 的账号不能建触发器和存储过程。本次只去掉 DEFINER 子句仍报 1419;给恢复账号 GRANT SET_USER_ID 后导入成功,DEFINER 保持原样。
mysqldump 恢复 200 万行要多久?
本次 MySQL 8.0.46、8 核 15 GB 的测试机,236 MB 备份(250 万行)导入 27.6 秒,整次演练约 33 秒。放宽刷盘、关 redo、按表并行都在 26.9–30.8 秒之间。不同机器差别很大,要用自己最大的表实测。
mysqldump 备份成功了,怎么确认它能用?
检查退出码、错误输出和文件末尾的 Dump completed;恢复到空实例后逐表比 CHECKSUM TABLE,再用 DEFINER 账号跑一遍视图、触发器、存储过程。本次备份中有 DDL 时报 1412、退出码 3,文件停在半截。