备份恢复 & 迁移30 分钟阅读
MySQL 备份与时间点恢复 100 条命令
MySQL 恢复到某一分钟,通常要先还原一份完整备份,再重放备份之后的 binlog。真正容易漏的是备份起点、binlog 文件是否连续,以及误操作到底落在哪个事件位置。只知道备份文件“还在”,恢复不一定做得成。
2026年9月16日阅读—点赞—收藏—
dba100mysqlscenario
100 条命令系列文章专栏
MySQL 恢复到某一分钟,通常要先还原一份完整备份,再重放备份之后的 binlog。真正容易漏的是备份起点、binlog 文件是否连续,以及误操作到底落在哪个事件位置。只知道备份文件“还在”,恢复不一定做得成。
MySQL 恢复到某一分钟,通常要先还原一份完整备份,再重放备份之后的 binlog。真正容易漏的是备份起点、binlog 文件是否连续,以及误操作到底落在哪个事件位置。只知道备份文件“还在”,恢复不一定做得成。
这篇按备份检查、日志定位、隔离实例恢复的顺序整理命令。需要恢复时可以从头对一遍;建议收藏,也可以分享给负责备份平台的同事。
MySQL 备份与 binlog 恢复链示意图
示例采用 Linux、MySQL 8.4、InnoDB 与逻辑全备。操作中的库名、路径和连接端都换成现场值。恢复命令仅在隔离实例演练,禁止把示例直接对准生产库。
1SHOW VARIABLES LIKE 'log_bin';若备份之后没有 binlog,就无法靠这条路线重放后续变化;不要在故障发生后才发现日志根本没写。
1SHOW BINARY LOG STATUS;记下文件名和 position。它是查询当下的写入位置,不等于旧备份的起点;旧备份要找当时随备份留存的坐标。
1SHOW BINARY LOGS;从备份起点一路对到目标时间,缺任何一段都不能假定后面的文件还能完整恢复。服务器列表也不包括已经移走的归档副本。
1SHOW VARIABLES LIKE 'binlog_expire_logs_seconds';期限太短会让全备还在、重放日志却已被清理。先把现场的异地归档策略对上,再评估可恢复窗口。
1SHOW VARIABLES WHERE Variable_name IN2 ('gtid_mode', 'binlog_format', 'sync_binlog');GTID 状态影响恢复时如何处理已经执行过的事务;sync_binlog 与日志落盘策略有关。记录当前配置,不要把参数值本身当作备份可用证明。
1mysqldump -h source-host -u backup_user -p \2 --all-databases --single-transaction --quick \3 --source-data=2 --routines --events --triggers \4 > /backup/full-20260916.sql--source-data=2 把导出实例的 binlog 坐标写成注释,导入时不会执行复制配置;备份账号须有 RELOAD 和查询日志状态所需权限。它与 --single-transaction 同用,开始时仍会短暂取得全局读锁。InnoDB 等事务表才有一致快照;导出期间别对被导出表做 DDL。口令由客户端交互输入,不写进命令行。
1grep -m 1 'CHANGE REPLICATION SOURCE TO' /backup/full-20260916.sql把文件名、位置抄到恢复记录中。若找不到这行,先查备份任务参数和备份头,不要拿当前 SHOW BINARY LOG STATUS 的位置替代。
1ls -lh /backup/full-20260916.sql2sha256sum /backup/full-20260916.sql文件存在和哈希一致只证明传输与存储没有明显损坏;SQL 能否导入、对象是否齐全仍需隔离恢复验证。
1ls -lh /archive/binlog.0001* > /backup/binlog-inventory.txt目录和文件模式只作示例。清单应覆盖备份坐标起到目标事件所在文件,逐个核对编号和校验值;单靠文件名连续不能证明内容完整。
1mysqlbinlog --base64-output=DECODE-ROWS -vv \2 /archive/binlog.000125 > /backup/binlog-000125-review.txt先把目标文件解码成只读文本,核对事件时间、end_log_pos 和事务边界。行格式事件的参数值可能被解码显示,文本要按生产数据权限保存;真正重放时别用这份人工查看文件替代原始 binlog。
1SHOW VARIABLES LIKE 'binlog_checksum';先记录源端的校验设置。归档文件传输后还要逐个校验,光看文件名和大小不能说明内容没损坏;校验值也不能代替恢复演练。
1mysqlbinlog --verify-binlog-checksum \2 /archive/binlog.000125 > /dev/null检查命令的退出码和报错,批量归档时应逐文件执行。这不连接恢复实例,也不重放数据;即使校验通过,仍要检查从备份起点到目标事件的文件是否连续。
1SHOW VARIABLES WHERE Variable_name IN2 ('server_id', 'server_uuid', 'log_bin_basename');同名 binlog.000125 可能来自不同实例,尤其在迁移和切主之后。先对源库 ID、UUID 和日志基名,再去拼归档链;不能把另一台库的文件按编号接到当前备份后面。
1SHOW BINLOG EVENTS IN 'binlog.000125'2 FROM 15432 LIMIT 30;文件名和 15432 要取自备份记录。Pos、End_log_pos 能帮你核对事件边界;这里只看 30 条,避免无 LIMIT 时把整份日志拉到客户端。源库已清理的日志要改从归档副本查。
1mysqlbinlog --start-position=15432 \2 --base64-output=DECODE-ROWS -vv \3 /archive/binlog.000125 \4 > /backup/from-full-backup-position.txt--start-position 必须是该文件的事件起始字节位置,不是时间或事件序号;多文件输入时它只作用于第一个文件。这份文本供人工核对,不是可直接用于恢复的行事件 SQL;注意文件可能包含敏感数据。
1mysqlbinlog --start-datetime='2026-09-16 13:50:00' \2 --stop-datetime='2026-09-16 14:10:00' \3 --base64-output=DECODE-ROWS -vv \4 /archive/binlog.000125 \5 > /backup/incident-window.txt时间参数按运行 mysqlbinlog 的机器本地时区解释,先与源库和应用日志时区对齐。时间窗口用于找到候选事务,真正划恢复边界仍要核对完整事务的事件位置;不能在事务中间随手截断。
1SELECT @@GLOBAL.gtid_executed AS gtid_executed,2 @@GLOBAL.gtid_purged AS gtid_purged;源库开启 GTID 时,这两个集合帮助区分已执行与已清理日志对应的事务,但它们是现在的状态,不是旧全备的 GTID 快照。恢复链要以备份里留存的 GTID/坐标和随后归档的 binlog 为准。
1grep -m 1 'GTID_PURGED' /backup/full-20260916.sql如果逻辑备份包含 GTID 语句,先保存它与第 7 条的 binlog 坐标,检查是否符合隔离实例的恢复方案。找不到不能凭源库当前 gtid_executed 补写旧备份集合;mysqldump 参数和源库 GTID 状态都要回查。
1SELECT @@GLOBAL.time_zone AS global_time_zone,2 @@SESSION.time_zone AS session_time_zone,3 @@system_time_zone AS system_time_zone;再在运行 mysqlbinlog 的机器上核对本地时区。误操作时间如果来自应用日志,还要看应用时区;多一层转换就可能把第 16 条窗口移到错误事件上。
1SHOW VARIABLES WHERE Variable_name IN2 ('binlog_format', 'binlog_row_image',3 'binlog_transaction_compression');ROW 事件、人类可读的 -vv 解码文本和原始可重放 binlog 是三回事。binlog_row_image 影响更新事件中记录的行列信息;若启用事务压缩,人工检查时还要留意 payload 的解包结果。参数只说明写入方式,不说明某个归档文件已被验证。
1SELECT @@hostname AS host_name, @@port AS port,2 @@server_uuid AS server_uuid,3 @@datadir AS data_directory;在演练实例执行并保存结果,再与恢复工单的主机、端口和数据目录对照。后面几条会导入或重放数据,只靠命令行里写了 restore-host 并不能保证客户端真的连对库。
1SELECT VERSION() AS mysql_version;逻辑备份中的系统表、DDL、字符集和 GTID 语句可能受版本差异影响。这里以相同 MySQL 8.4 环境演练;跨版本恢复要单独验证,不能把一次导入成功当成全部对象兼容。
1SHOW DATABASES;全库 dump 会包含多个库及可能的系统对象。目标应是按恢复方案准备的隔离实例,不能带着未知业务数据直接导入;看到已有同名库,先核对来源和是否允许覆盖。
1head -n 80 /backup/full-20260916.sql看 dump 是否完整写出了客户端版本、会话设置及第 7 条的复制坐标。头部能帮助发现拿错文件,但不能代替检查文件末尾、对象清单和实际导入;备份内容按生产数据权限保管。
1mysql --binary-mode -h restore-host -P 3307 \2 -u recovery_user -p \3 < /backup/full-20260916.sql只对第 21 条确认过的隔离实例执行。 这会创建或覆盖 dump 中的对象;导入前确认它是相容版本、目标数据可丢弃、用户有必需权限。记录退出码和报错,不要用“客户端没打印错误”代替对象回读。
1SELECT TABLE_SCHEMA, TABLE_NAME, ENGINE,2 TABLE_ROWS3FROM information_schema.TABLES4WHERE TABLE_SCHEMA = 'appdb'5 AND TABLE_NAME IN ('orders', 'customers');先核对核心对象和存储引擎;TABLE_ROWS 对 InnoDB 是估计行数,不能据此证明全备记录一条不缺。还要对照备份清单、关键表实际计数和业务校验值。
1mysqlbinlog --start-position=15432 \2 --stop-position=78231 \3 /archive/binlog.000125 \4 > /backup/replay-preview.sql这份输出保留了可重放事件,不是第 10 条的 DECODE-ROWS 人工解码文本。15432 是全备起点,78231 应是经过事件检查确定的停止边界;预览时核对 GTID、事务起止和误操作事件是否被排除,文件要妥善保管。
1set -o pipefail2mysqlbinlog --disable-log-bin \3 --start-position=15432 --stop-position=78231 \4 /archive/binlog.000125 \5 | mysql --binary-mode -h restore-host -P 3307 \6 -u recovery_user -p只在隔离实例执行。 停止位置必须落在完整事务边界,并在要跳过的误操作事件之前;按官方建议用位置而非时间截重放范围。pipefail 让日志解码失败也反映在命令退出码里。--disable-log-bin 需要允许设置会话 sql_log_bin 的权限;GTID 与目标实例已有事务集合须先核对,报错后不要盲目从头重放。
1SELECT @@GLOBAL.gtid_executed AS replayed_gtids,2 @@GLOBAL.gtid_purged AS purged_gtids;与全备的 GTID 起点和预期重放事务对照,确认没有把误操作事务也导入。GTID 集合对得上仍不能证明业务结果正确;如果实例未启用 GTID,改用事件坐标、导入日志与业务数据核对。
1SELECT COUNT(*) AS order_count,2 MIN(created_at) AS first_order,3 MAX(created_at) AS last_order4FROM appdb.orders;在隔离实例执行,把结果与备份时点和目标时间点的业务基线比。计数、最早/最晚时间只是第一层校验;误删或误改场景还应查受影响主键、状态、金额及关联表,确认恢复边界确实停在错误操作之前。
1sha256sum -c /backup/binlog-sha256.manifest清单须在归档时生成,覆盖本次用到的每个 binlog;现在才对损坏文件算哈希没有比较价值。校验失败先停下恢复演练,找另一份可信副本,不要绕过失败文件直接接后面的日志。
1for f in /archive/binlog.000125 /archive/binlog.000126; do2 mysqlbinlog --verify-binlog-checksum "$f" > /dev/null || exit 13done这条检查两份示例文件都能读完并通过事件校验。实际文件按备份起点至目标事件逐个列出;批量命令失败时保存出错文件名和错误输出。校验通过还不能证明中间没有漏文件,也不能证明 GTID 或事务边界正确。
1mysqlbinlog --start-position=15432 \2 /archive/binlog.000125 /archive/binlog.000126 \3 > /backup/two-binlogs-review.sql--start-position 只限制第一份文件,后面的文件从开头读取。用归档清单和 Rotate 事件核对文件先后,再在输出里找目标事务。预览包含可能被误操作影响的事件,文件仅供受控检查,不要直接导入恢复库。
1mysqlbinlog --start-position=15432 \2 --stop-position=88210 \3 /archive/binlog.000125 /archive/binlog.000126 \4 > /backup/two-binlogs-replay-preview.sql--stop-position 作用于最后一份文件。这里的 88210 应从第 33 条核对出的完整事务边界取得,不能凭故障时间直接换算成字节位置。确认预览中已经包含应该恢复的最后一笔事务,且没有包含要避开的误操作。
1grep -n -E '^# at |end_log_pos|COMMIT' \2 /backup/two-binlogs-replay-preview.sql \3 > /backup/replay-boundaries.txt先对文件切换点、目标事件的 # at 和事务结束记录。COMMIT 文本筛选只是辅助;ROW 事件和压缩事务的输出形式可能不同,不能用“搜到了 COMMIT”单独认定截断位置安全。还要看上下文和原始事件。
1set -o pipefail2mysqlbinlog --disable-log-bin \3 --start-position=15432 --stop-position=88210 \4 /archive/binlog.000125 /archive/binlog.000126 \5 | mysql --binary-mode -h restore-host -P 3307 \6 -u recovery_user -p只对第 21 条核实的隔离实例执行;两份文件按顺序一次输入,避免手工拼接时跳过或重复重放事件。目标若已跑过其中一段,不要原样从起点再执行;先查退出码、错误日志、GTID 集合和恢复数据,再制定续跑边界。
1SHOW VARIABLES LIKE 'binlog_encryption';加密的 binlog 不能按普通归档文件直接由 mysqlbinlog 解码。若现场源库还保留文件,可按授权流程从服务器读取;已经离线的归档副本要核对备份平台是否保存了可恢复的解密材料。不能把“本地工具读不了”误判为文件损坏。
1mysqlbinlog --read-from-remote-server \2 -h source-host -u binlog_reader -p \3 binlog.000125 > /backup/remote-binlog-review.sql仅在源服务器仍有该文件、账号有读取 binlog 权限时使用;这条把事件输出到本地检查文件,不对任何数据库重放。从服务器取得的文本可能包含业务数据,要按生产数据保护。远端读到的文件也要和备份时记录的源库身份、事件范围对上。
1SELECT @@GLOBAL.gtid_mode AS gtid_mode,2 @@GLOBAL.server_uuid AS restore_uuid,3 @@GLOBAL.gtid_executed AS executed_gtids;在隔离实例执行。后面的集合比较只适用于 GTID 方案;gtid_mode 没开启时,必须回到文件、事件位置和恢复日志判断续跑点。restore_uuid 要与源库的 UUID 区分,避免把本机新生成的事务误认作源库恢复事务。
1SELECT GTID_SUBTRACT(2 '替换为备份起点到停止边界的预期 GTID 集合',3 @@GLOBAL.gtid_executed4) AS not_yet_replayed;预期集合必须从经过核对的 binlog 范围整理,不能拿源库现在的 gtid_executed 代替。结果列出仍未在隔离实例执行的 GTID;它只能判断事务标识差集,不能告诉你这些事务内部的业务数据是否正确。
1SELECT GTID_SUBTRACT(2 @@GLOBAL.gtid_executed,3 '替换为全备 GTID 与预期重放 GTID 的并集'4) AS extra_gtids;全备导入时可能已经设置过 GTID 集合,因此要把全备起点也算进去。结果非空时先分清是隔离实例自己产生的事务、误操作事务,还是恢复范围写错;不能仅凭“多出几个 GTID”就删改系统表。
1SELECT GTID_SUBSET(2 '替换为目标时间前应恢复的 GTID 集合',3 @@GLOBAL.gtid_executed4) AS all_expected_gtids_present;返回 1 表示该 GTID 集合已包含在恢复实例中;返回 0 时用第 40 条找具体缺口。集合包含关系不等于数据校验通过,还要执行第 30 条的业务表与主键核对。
1mysqlbinlog \2 --include-gtids='替换为已确认的目标 GTID 集合' \3 /archive/binlog.000125 /archive/binlog.000126 \4 > /backup/selected-gtids-review.sql这条只生成检查文件,不直接重放。GTID 过滤适合核对选中的事务实际位于哪些文件、是否含目标业务变更;如果过滤后漏掉必要的非 GTID 事件或相关 DDL,不能把这份输出当作完整恢复链。
1mysqlbinlog \2 --exclude-gtids='替换为隔离实例已确认执行的 GTID 集合' \3 /archive/binlog.000125 /archive/binlog.000126 \4 > /backup/unreplayed-gtids-review.sql中断后先核对目标实例与预期集合,再用这个选项检查剩余事件。不能把源库当前 GTID 集合直接填进去,也不能以“排除已执行”替代完整的文件和位置边界;输出仍需人工检查事务依赖与最终停止事件,才谈得上续跑。
1grep -n -m 3 -E 'GTID_PURGED|gtid_purged|sql_log_bin' \2 /backup/full-20260916.sqlmysqldump 默认可能把源端已执行 GTID 集合写入备份,并在导入时关闭当前会话的 binlog。全库备份和只导出部分库的含义不同:部分库 dump 的 GTID 集合也可能包含其他库的事务。导入前先确认文件里的具体语句,不能见到“GTID 已包含”就跳过 binlog 回放。
1tail -n 8 /backup/full-20260916.sqlMySQL 逻辑 dump 通常在尾部留下完成时间注释。缺少结尾或只剩半条 SQL,先查备份任务退出码、传输及文件校验;看到完成标记也不能代替第 25、26 条的实际导入测试。
1-- 在源库执行;排除系统 schema2SELECT TABLE_SCHEMA, TABLE_NAME, ENGINE3FROM information_schema.TABLES4WHERE TABLE_TYPE = 'BASE TABLE'5 AND TABLE_SCHEMA NOT IN ('mysql', 'sys',6 'information_schema',7 'performance_schema')8 AND ENGINE <> 'InnoDB'9ORDER BY TABLE_SCHEMA, TABLE_NAME;第 6 条使用 --single-transaction,它只能给事务表提供一致快照。非 InnoDB 表出现结果时,要检查备份窗口是否有写入,必要时另定锁表或停写策略;不能把整个 dump 直接称为同一时刻的全库快照。
1mysqlbinlog --start-datetime='2026-09-16 02:00:00' \2 --stop-datetime='2026-09-16 02:30:00' \3 /archive/binlog.000125 \4 > /backup/backup-window-ddl-review.sql先用备份实际开始、结束时间缩小范围,再在输出中查 ALTER TABLE、DROP TABLE 等 DDL。时间参数按检查机本地时区解释,事件可能跨相邻文件;加密 binlog 还须按第 37、38 条从服务器读取。这一条只是准备审阅材料,不能靠一份文件“没看到 DDL”就认定备份快照安全。
1-- 只在隔离恢复实例执行2SELECT @@GLOBAL.gtid_executed AS before_import_executed,3 @@GLOBAL.gtid_purged AS before_import_purged,4 @@GLOBAL.server_uuid AS restore_uuid;要在第 25 条导入前保存这份读数,之后才分得清 dump 写入的 GTID 与隔离实例自己生成的事务。目标若不是全新实例,先核对旧 GTID、已有对象与备份来源,不能直接导入覆盖。
1-- 把 dump 中确认的 GTID_PURGED 集合填入参数2SELECT GTID_IS_DISJOINT(3 '替换为备份文件中的 GTID 集合',4 @@GLOBAL.gtid_executed5 ) AS no_overlap;返回 1 表示两套 GTID 没有交集;返回 0 要先查是不是此前已导入过这份备份。集合不重叠只是必要检查之一,仍要核对数据、库名和源端 UUID。不要为消除重叠而在现有恢复实例上随意重置 GTID。
1-- 在隔离恢复实例执行,按业务 schema 核对2SELECT 'routine' AS object_type, COUNT(*) AS object_count3FROM information_schema.ROUTINES4WHERE ROUTINE_SCHEMA = 'appdb'5UNION ALL6SELECT 'trigger', COUNT(*)7FROM information_schema.TRIGGERS8WHERE TRIGGER_SCHEMA = 'appdb'9UNION ALL10SELECT 'event', COUNT(*)11FROM information_schema.EVENTS12WHERE EVENT_SCHEMA = 'appdb';第 6 条带了 --routines --events --triggers,导入后还应按业务库逐项核对。数量一致只能说明对象个数大致齐全,不能证明定义、DEFINER 权限和事件调度状态都正确。
1SELECT ENGINE, COUNT(*) AS table_count2FROM information_schema.TABLES3WHERE TABLE_SCHEMA = 'appdb'4 AND TABLE_TYPE = 'BASE TABLE'5GROUP BY ENGINE6ORDER BY table_count DESC;与第 47 条源库清单对比,确认非事务表没有在导入时消失或变成别的引擎。恢复时间点的业务表数量、主键数据还要按第 30 条检查;只看总表数和引擎分布不能证明 PITR 已经成功。
1SHOW VARIABLES WHERE Variable_name IN2 ('binlog_expire_logs_auto_purge',3 'binlog_expire_logs_seconds');第 4 条看保留秒数,这里连自动清理开关一起看。关闭自动清理能暂时保留日志,但不是长期归档方案;手工 PURGE BINARY LOGS 仍可能删除文件。恢复窗口要覆盖最近一次可用全备到目标时刻。
1SHOW VARIABLES LIKE 'binlog_error_action';默认 ABORT_SERVER 会在严重 binlog 写入错误时让实例关闭。IGNORE_ERROR 则可能停掉日志却继续接受更新,这会让后续 PITR 链断掉。查到它还要对照错误日志和当前 log_bin 状态,不能只凭设置判断过去没发生过故障。
1SHOW VARIABLES WHERE Variable_name IN2 ('sync_binlog', 'innodb_flush_log_at_trx_commit');sync_binlog=1 与 innodb_flush_log_at_trx_commit=1 是 InnoDB 事务与 binlog 一致性、持久性的常见基线。值放宽时,断电或操作系统崩溃后可能缺少已提交事务的 binlog;即使两项为 1,仍需核对存储设备的真实刷盘保证。
1SHOW VARIABLES LIKE 'source_verify_checksum';启用后,源端读取 binlog 事件会检查校验值,发现不匹配就报错停止;它默认可能关闭。第 12、32 条是归档副本的本地检查,不能被这一项替代。先记录现场值,不要在恢复途中随意改变源库设置。
1mysqlbinlog --read-from-remote-server --raw \2 --result-file=/archive/ \3 -h source-host -u binlog_reader -p \4 binlog.000125--raw 输出可再次交给 mysqlbinlog 的二进制日志文件,适合源端加密或本地副本尚未留存时补归档。先确认 /archive/ 路径与文件命名,避免覆盖已有归档。源端加密文件经 mysqlbinlog 复制后会成为未加密副本;传输按第 59 条验证 TLS,归档目录也须保护。活动文件还在增长,单次副本不能当最终封存版本。
1mysqlbinlog --read-from-remote-server --to-last-log \2 --start-position=15432 \3 -h source-host -u binlog_reader -p \4 binlog.000125 > /backup/remote-chain-review.sql只生成检查文件,不发送到恢复实例。--start-position 作用于第一个文件,--to-last-log 继续读到源端当前最后一个 binlog;先保存文件清单和停止事件,不能把“读到最后”直接当作目标 PITR 范围。
1mysqlbinlog --read-from-remote-server \2 --ssl-mode=VERIFY_IDENTITY --ssl-ca=/secure/mysql-ca.pem \3 -h source-host -u binlog_reader -p \4 binlog.000125 > /backup/tls-binlog-review.sql源库启用 binlog 加密时,远端事件流可解密传给客户端,连接本身必须保护。VERIFY_IDENTITY 还要求证书链与主机名正确;示例 CA 路径按现场替换。输出文本同样可能包含业务数据,不能因为传输加密就把检查文件放到公共目录。
1mysqlbinlog --read-from-remote-server --raw --stop-never \2 --connection-server-id=900123 \3 --result-file=/archive/ \4 -h source-host -u binlog_reader -p \5 binlog.000125这是长时间运行的归档任务,不是一次性恢复命令。连接 ID 必须避开已有副本或归档进程;还要监控任务存活、文件落盘、轮转、归档目录空间,以及断线后的重启续接。mysqlbinlog 断开后不会自动重连;源端加密时归档副本仍是未加密文件。停掉任务之后,不能拿最后一个正在写的文件当作完整恢复链。
1SHOW BINARY LOG STATUS\G除了 File 和 Position,还要看 Binlog_Do_DB、Binlog_Ignore_DB。这里若有库名,全备中的某些变更可能根本没写进 binlog。按库过滤的效果还受 STATEMENT、ROW 格式影响;先核对备份范围与业务跨库事务,不能只用一条筛选过的日志证明全库可以恢复。
1SHOW GLOBAL VARIABLES LIKE 'binlog_row_image';FULL 记录完整行列,MINIMAL 主要记录定位行所需的列和变化列,NOBLOB 会省掉不需要的 BLOB/TEXT 列。第 20 条同时看格式和压缩;这里单独确认行镜像,是为了审阅 UPDATE、DELETE 事件时不把缺少的列误判为数据丢失。历史日志可能由不同的会话设置写成,当前全局值不能替代实际事件检查。
1SHOW GLOBAL VARIABLES LIKE 'binlog_row_metadata';默认 MINIMAL 不带完整列名等信息;FULL 会让离线解析行事件更方便。这个变量不改变恢复实例对事件的正常重放,但影响人能否把 @1、@2 对回表的实际列。查旧文件时,先以当时的表定义和日志事件为准。
1mysqlbinlog --print-table-metadata \2 /archive/binlog.000125 > /backup/table-metadata-review.sql只有源端写入过的元数据才能打印出来;第 63 条若为 MINIMAL,这里也不会凭空补出完整列名。检查文件放在受控目录,行事件和语句事件可能带业务数据。此文件供审阅,不作为经过改写的重放脚本。
1mysqlbinlog --database=appdb \2 /archive/binlog.000125 > /backup/appdb-filter-review.sql这只用于检查过滤会漏掉什么,不用于整库 PITR。语句日志按当时的默认数据库筛选,跨库 SQL 可能被意外包含或排除;行日志按被修改的表所属库筛选。先与第 33 条完整日志预览对照,恢复时按全备所覆盖的事务链重放。
1SHOW GLOBAL VARIABLES LIKE 'binlog_transaction_compression';启用时,一笔事务的负载可能以 Transaction_payload_event 写进 binlog。查看原始事件列表时别把它当成“只有一个业务操作”;用与源库版本匹配的 mysqlbinlog 解码,再核对事务内的实际变更。当前设置也不能证明旧文件全部采用同一格式。
1SHOW GLOBAL VARIABLES LIKE 'binlog_rows_query_log_events';ON 时,行事件旁边可能多一条供排查的原始语句,使用 mysqlbinlog -vv 才容易看到。它是说明信息,正常重放不会把这条语句再执行一遍;OFF 时也不能从行变更反推出完整业务 SQL。旧日志是否有这类事件,要以实际文件为准。
1SHOW GLOBAL VARIABLES LIKE 'binlog_row_value_options';出现 PARTIAL_JSON 时,JSON 修改后的行镜像可能只写改动部分。mysqlbinlog --verbose 可以把部分更新显示成供审阅的伪 SQL,重放仍须使用保留原始事件的输出。恢复实例若已有同一文档的不同版本,部分更新即使执行成功,也不代表结果与源库一致;要抽查关键 JSON 值。
1SHOW GLOBAL STATUS WHERE Variable_name IN2 ('Binlog_cache_use', 'Binlog_cache_disk_use',3 'Binlog_stmt_cache_use', 'Binlog_stmt_cache_disk_use');*_disk_use 是累计溢出次数,不是归档文件丢失数。数值持续增长时,留意大事务、临时目录容量和备份窗口的写入峰值;一次 PITR 仍要靠具体 binlog 文件及事件校验来判断能否恢复。
1grep -n -C 15 -E 'GTID_NEXT|Xid =|DROP TABLE' \2 /backup/incident-window.txt第 16 条只用时间缩小了范围。这里接着找误操作前后的 # at、end_log_pos、GTID 和提交标记,再回到完整日志确认它们属于哪笔事务。grep 的上下文可能截掉事务开头或结尾;行日志里的伪 SQL 也不能当作可重放语句。最终停止位置要落在待跳过事务的起点,不能只凭发现 DROP TABLE 的一行确定。
1mysqlbinlog --start-position=355 \2 /archive/binlog.000125 > /backup/after-error-review.sql官方 PITR 示例用错误事件的结束位置作为后续事件的起点,适合评估“恢复到误操作之前,再保留后面的更新”。355 只是示例位置,须先确认它正好是坏事件所在事务组的结束边界;如果一笔事务包含多项变更,不能只跳过其中一个行事件。先检查后续事务是否依赖被跳过的数据和对象,尤其不能把这份预览直接接到生产库执行。
1SHOW GRANTS FOR CURRENT_USER;在恢复实例上用真正执行重放的账号连接后再查。行事件生成的 BINLOG 语句需要相应的 BINLOG_ADMIN,或 REPLICATION_APPLIER 加事件涉及对象的权限;能导入 dump 不等于能回放行日志。角色授予和当前启用的角色也要核对,权限不足时先处理账号,别用生产 root 密码绕过去。
1SELECT LOGGED, PRIO, ERROR_CODE, SUBSYSTEM, DATA2FROM performance_schema.error_log3WHERE PRIO IN ('Error', 'Warning')4ORDER BY LOGGED DESC5LIMIT 50;重点找导入、重放、InnoDB 启动和权限报错。这个表是否有内容取决于实例的错误日志配置,而且只保留近期事件;查不到不等于没报错。还要看客户端退出码和实例实际错误日志,时间按当前 SQL 会话时区显示。
1CHECK TABLE appdb.orders;检查结果若有 error、warning,先留存内容再分析。InnoDB 支持 CHECK TABLE,但大表检查会耗时且影响访问;它能发现一部分表问题,不能证明订单金额、状态、关联关系都回到了目标时间点。不要看到 OK 就省掉第 30 条的业务数据核对。
1SHOW GLOBAL VARIABLES LIKE 'event_scheduler';全备带了 --events,导入后的计划任务可能会在隔离实例运行。演练通常先让调度器保持 OFF,否则清理任务、汇总任务可能在 PITR 还没验收时修改数据。DISABLED 不能在运行时切换;若需要改设置,先核对实例用途和其他任务。
1SELECT EVENT_SCHEMA, EVENT_NAME, STATUS,2 DEFINER, TIME_ZONE, LAST_EXECUTED3FROM information_schema.EVENTS4WHERE EVENT_SCHEMA = 'appdb'5ORDER BY EVENT_NAME;第 51 条只数对象个数,这里逐项看启用状态、执行账号、时区和最近执行时间。恢复实例里 LAST_EXECUTED 的值还可能来自导入后的本机执行;把清单与源库备份时的定义对上,再决定是否恢复调度,不能在验证数据之前就打开它。
1SHOW CREATE TABLE appdb.orders\G将列、索引、约束、引擎与目标时点的 DDL 记录比。误操作若改的是表结构,行数和 GTID 都可能看起来正常,实际应用却访问了另一套定义。第 48 条检查备份窗口 DDL,第 76 条检查计划任务;这里确认最终对象是什么。
1SELECT @@GLOBAL.read_only AS read_only,2 @@GLOBAL.super_read_only AS super_read_only;这个值只说明实例当前的保护设置,不证明整个演练期间都没有别的账号写入。隔离恢复结束后,若还要给业务方抽查数据,应先确定外部连接不会改动结果;super_read_only 会挡住有高权限的客户端写入,但更改它还可能影响事件调度器和正在运行的事务。不要把开关直接照搬到正式恢复库。
1SELECT TABLE_NAME, COLUMN_NAME,2 REFERENCED_TABLE_SCHEMA, REFERENCED_TABLE_NAME,3 REFERENCED_COLUMN_NAME, CONSTRAINT_NAME4FROM information_schema.KEY_COLUMN_USAGE5WHERE TABLE_SCHEMA = 'appdb'6 AND TABLE_NAME IN ('orders', 'order_items')7 AND REFERENCED_TABLE_NAME IS NOT NULL8ORDER BY TABLE_NAME, CONSTRAINT_NAME, ORDINAL_POSITION;把结果与目标时点的建表语句核对。订单主表恢复了、明细表外键却指向另一列,这类问题不会通过简单行数检查发现。无结果可能是业务本来就没建外键,也可能是恢复时缺了约束;不能直接判断数据关系正常。
1SELECT TRIGGER_NAME, EVENT_OBJECT_TABLE,2 EVENT_MANIPULATION, ACTION_TIMING,3 ACTION_ORDER, DEFINER, ACTION_STATEMENT4FROM information_schema.TRIGGERS5WHERE TRIGGER_SCHEMA = 'appdb'6 AND EVENT_OBJECT_TABLE IN ('orders', 'order_items')7ORDER BY EVENT_OBJECT_TABLE, EVENT_MANIPULATION,8 ACTION_TIMING, ACTION_ORDER;第 51 条只核对数量;这里要看触发时机、顺序、执行账号和实际逻辑。恢复后的新写入可能触发账务或审计变更,因此验收之前别直接让应用连接演练实例。若源端备份时已经有触发器,目标时点还发生过 DDL,更要逐项对定义。
1SHOW CREATE PROCEDURE appdb.close_order\G示例过程名按业务系统替换。对照备份版本和目标时点的 DDL,重点看 DEFINER、SQL 模式及过程体。普通 EXECUTE 权限可能只能让你看到空的定义字段;出现 NULL 先核对查看权限,不要把它当成“过程内容丢了”。
1SELECT order_id, customer_id, status, total_amount, created_at2FROM appdb.orders3WHERE order_id IN (100182, 100183, 100184)4ORDER BY order_id;这些主键只是示例,现场应从事故记录或应用审计里取。第 30 条是总量检查;误改常常只波及几行,应把恢复结果与事故之前可信的订单状态、金额和客户归属逐条比较。源库事故后的数据不能当验收基线。
1SELECT i.order_id, COUNT(*) AS item_count2FROM appdb.order_items AS i3LEFT JOIN appdb.orders AS o ON o.order_id = i.order_id4WHERE o.order_id IS NULL5GROUP BY i.order_id6ORDER BY item_count DESC7LIMIT 20;这是一条按业务关系写的抽查 SQL,表名和关联键须按现场改。查到记录,先分清是恢复链截断、原本就存在的脏数据,还是业务允许的孤立明细;无结果只覆盖这一组关系,不代表全库数据一致。
1SHOW CREATE VIEW appdb.v_order_summary\G视图名按业务系统替换。把查询定义、DEFINER 和 SQL SECURITY 与目标时点的版本核对;表能查、视图报错,可能是视图依赖的列或执行账号出了问题。只查视图返回的行数发现不了这类定义差异。
1SELECT DATE(created_at) AS order_day, status,2 COUNT(*) AS order_count,3 SUM(total_amount) AS total_amount4FROM appdb.orders5WHERE created_at >= '2026-09-15 00:00:00'6 AND created_at < '2026-09-17 00:00:00'7GROUP BY DATE(created_at), status8ORDER BY order_day, status;日期和金额列按现场替换,并与事故前的对账报表或审计流水比。第 30 条只看总量;误删一批订单或误改金额时,按天、状态拆开更容易看到缺口。汇总吻合仍不能覆盖单笔错账,第 82 条的受影响主键还要逐条验。
1mysqldump --single-transaction --quick \2 --routines --events --triggers --set-gtid-purged=OFF \3 --no-tablespaces -h restore-host -P 3307 \4 -u recovery_user -p --databases appdb \5 > /backup/verified-pitr-appdb.sql这是验收后的留档,不替代原始全备和连续 binlog。--set-gtid-purged=OFF 不把恢复实例的全局 GTID 集合写进这份业务库副本;GTID、起止文件位置和核对结果须单独记录,否则以后可能不知道它恢复到了哪一笔事务。实例若还在运行 DDL,--single-transaction 也不能保证定义一致;先固定演练环境,再保存退出码、文件大小与校验值。
1wc -c /backup/verified-pitr-appdb.sql2sha256sum /backup/verified-pitr-appdb.sql \3 > /backup/verified-pitr-appdb.sha256先确认第 86 条 mysqldump 正常结束,再记录文件大小与哈希。哈希用于证明以后拿到的是同一份验收副本,不证明恢复内容本身正确;要把第 82—85 条的业务核对结果、原始备份坐标和重放停止位置放在同一份恢复记录里。
1SHOW CREATE EVENT appdb.daily_order_rollup\G第 76 条列出任务状态,这条进一步看执行时间、DEFINER 和任务体。示例名称按现场替换。恢复实例里即使调度器是 OFF,以后若要接业务流量,也要确认这个任务会不会重跑历史日期、覆盖已经验收的结果。
1SELECT ROUTINE_NAME, ROUTINE_TYPE,2 SECURITY_TYPE, DEFINER, SQL_MODE3FROM information_schema.ROUTINES4WHERE ROUTINE_SCHEMA = 'appdb'5ORDER BY ROUTINE_TYPE, ROUTINE_NAME;第 81 条看单个关键过程的定义,这里扫一遍 DEFINER 和 INVOKER 的执行方式。导入时如果改过属主,过程存在也可能在业务调用时因权限报错;定义字段看不全时先确认查看权限。别在验收库上靠随意改 DEFINER 消除报错,而不核对源库授权方案。
1set -o pipefail2mysqlbinlog --disable-log-bin --start-position=355 \3 /archive/binlog.000125 \4 | mysql --binary-mode -h restore-host -P 3307 \5 -u recovery_user -p只在重新建立的隔离恢复实例演练。 355 应是第 71 条确认过的坏事务结束边界,不能把它当固定值。先恢复到误操作之前,确认后续事务不依赖被跳过的修改,再决定是否继续;一旦继续回放,目标不再是“停在事故前”,而是“跳过事故后追上后续变更”。记录 GTID、退出码和最终业务对账结果,任何一项不对就保留原演练实例,别直接覆盖它重来。
1SHOW REPLICA STATUS\G只在备份来源确实是副本时用。看 Replica_SQL_Running、Last_SQL_Error、Relay_Source_Log_File、Exec_Source_Log_Pos 和 GTID 集合,分清日志已经接收与事务已经应用。最好在备份任务开始和结束时留存这份状态;故障之后查到的当前位置,不能倒推旧全备采样时副本落在哪笔事务。多线程副本还要留意执行位置与最近已提交事务不一定完全等价。
1SHOW GLOBAL VARIABLES LIKE 'log_replica_updates';如果全备从副本导出,而后续 binlog 也打算从该副本取,OFF 就可能让副本自己的日志缺少上游事务;这份链不能直接拿来做全库 PITR。还要同时确认 log_bin=ON、备份里记录的副本本地日志坐标与归档副本一致。这个值是当前配置,旧备份仍需回查当时的配置和日志文件。
1SELECT CHANNEL_NAME, FILTER_NAME, FILTER_RULE,2 CONFIGURED_BY, ACTIVE_SINCE3FROM performance_schema.replication_applier_filters4ORDER BY CHANNEL_NAME, FILTER_NAME;多源副本更要对准备份所用的通道。只备份副本已有数据,原本被 REPLICATE_IGNORE_DB 等规则排除的库就不会凭 PITR 自己长出来;后续日志也须来自同一套数据范围。无结果表示当前未列出通道级规则,还得查全局规则和备份时的配置。
1SELECT FILTER_NAME, FILTER_RULE,2 CONFIGURED_BY, ACTIVE_SINCE3FROM performance_schema.replication_applier_global_filters4ORDER BY FILTER_NAME;把第 93、94 条的规则与全备导出的库和表逐项比。行事件与语句事件按库过滤的判断依据不同,DDL 即使源端是 ROW 格式也按语句规则处理;所以只看 appdb 当前表数,可能仍漏了目标时点的表结构变更。这里展示的是当前规则,旧备份要靠当时的配置、导出清单和日志核实。
1SELECT CHANNEL_NAME, WORKER_ID, SERVICE_STATE,2 LAST_APPLIED_TRANSACTION, APPLYING_TRANSACTION,3 LAST_ERROR_NUMBER, LAST_ERROR_MESSAGE4FROM performance_schema.replication_applier_status_by_worker5ORDER BY CHANNEL_NAME, WORKER_ID;多线程副本不能只看一个总体延迟值。某个 worker 报错或还在应用事务时,从它取的全备要对照备份时间和一致性快照重新判断。这里的 LAST_APPLIED_TRANSACTION 是该线程最近一笔,并非全通道最终一致的恢复坐标;状态还会随 START REPLICA 等操作变化,旧备份要找当时留存的记录。
1mysqldump --single-transaction --quick --skip-triggers \2 --set-gtid-purged=OFF --no-tablespaces \3 --where='order_id IN (100182,100183,100184)' \4 -h restore-host -P 3307 -u recovery_user -p \5 appdb orders > /backup/affected-orders.sql只对已验收的隔离恢复库执行,主键清单须来自事故核查。这个文件包含 orders 的表定义和筛出的行,不包含对应的 order_items 或其他关联表;不能把它当完整业务恢复包。--skip-triggers 防止导到临时验证库时再创建业务触发器,--set-gtid-purged=OFF 避免把整库 GTID 集合塞进部分数据文件。先检查 mysqldump 退出码和导出行数。
1CREATE DATABASE pitr_review;这条只在空白隔离验证实例执行,库名按现场换。先确认实例地址、没有同名业务库,并保留现有库清单;正式生产库里不要用临时库承接恢复数据。验证实例单独准备,能避免第 96 条源恢复库在抽查期间被导入动作改掉。
1mysql --binary-mode -h scratch-host -P 3307 \2 -u recovery_user -p pitr_review \3 < /backup/affected-orders.sql第 96 条按“数据库名 + 单表名”导出,没有 --databases,文件不会主动切回 appdb;仍要在导入前检查头部和 CREATE TABLE 语句,确认目标是刚建的 pitr_review。如果表定义或数据导入失败,保存完整报错,不要忽略错误继续交付。
1SELECT order_id, customer_id, status,2 total_amount, created_at3FROM pitr_review.orders4ORDER BY order_id;与第 82 条恢复库读数及事故前的可信审计记录对照。还要确认导出的主键数量与第 96 条过滤条件一致;这里仅验证订单主表,外键关联、明细、支付和审计数据若受影响,应另行抽取并核对。交付前把最终处理方案交给业务方确认,不能直接拿文件覆盖生产表。
1wc -c /backup/affected-orders.sql2sha256sum /backup/affected-orders.sql \3 > /backup/affected-orders.sha256把文件大小、哈希、第 99 条逐条核对结果和恢复来源坐标放在一起。第 87 条校验的是整份验收副本,这里校验筛出的交付文件;两份文件不能混用。哈希只证明传递过程中内容是否变化,不能证明筛出的行就是事故所需的全部数据。
MySQL 做时间点恢复,别先按故障时间截 binlog。全备坐标和日志文件先对齐,再按事件位置确认要停在哪笔事务前。恢复放在隔离实例做,最后用受影响的主键、金额和关联数据验收。若全备取自副本,还要查副本应用位置、日志回写和复制过滤;只恢复几行时,先在第二个隔离库验证导出文件。
ORA100 DBA100 命令系列海报
其他数据库的备份恢复与运维专题放在 ORA100 · DBA100:https://ora100.com/dba100
微信小程序搜索 「三笠的百令册」,也能查这套命令。