数据库基础体系 · 第 89/139 篇。文章以各产品官方稳定版本的公开语义为准;示例会明确引擎、事务与部署边界。
MySQL 备份恢复:逻辑备份、物理备份、Binlog 与 PITR 演练
在 MySQL 中,“备份成功”不等于“能够恢复到业务需要的时间点”。备份恢复至少包含四个不同问题:
- 如何保存某一时刻已经提交的数据;
- 如何保存表结构、账号、存储过程等数据库对象;
- 如何保存全量备份之后发生的增量变化;
- 发生误删、误更新或实例损坏后,如何把数据恢复到指定时间点。
逻辑备份、物理备份和 Binlog 分别解决不同层次的问题。PITR(Point-in-Time Recovery,时间点恢复)则把全量备份与 Binlog 组合起来,恢复到某个历史时刻。
本文以 MySQL 8.4、主要使用 InnoDB、单实例部署为基础说明。涉及复制、云存储、容器卷和非 InnoDB 引擎时,会明确额外边界。
一、先建立恢复模型:备份保存的到底是什么
设数据库在时间 的已提交状态为:
如果在时间 做了一份一致的全量备份,记为:
之后 Binlog 记录了一系列提交事件:
那么时间点恢复的目标是:
这个公式成立需要同时满足以下条件:
B_t0确实代表某个一致的数据库状态;- Binlog 覆盖了从备份一致性点到目标时间点的全部提交;
- Binlog 文件没有损坏、丢失或被错误过滤;
- 恢复过程按正确顺序重放;
- 目标实例的版本、字符集、表结构和运行条件足以解释这些事件;
- 恢复时没有把目标时间点之后的事件也重放进去。
因此,PITR 不是“把几个文件复制回来”,而是一个有起点、有连续日志、有终点的重放过程。
1. 备份的一致性点
“一致性备份”表示备份中的多个表能够共同描述某一时刻的数据库状态。例如转账事务:
START TRANSACTION;
UPDATE account
SET balance = balance - 100
WHERE id = 1;
UPDATE account
SET balance = balance + 100
WHERE id = 2;
COMMIT;
如果备份读到了第一条 UPDATE,却没有读到第二条 UPDATE,恢复后的数据就不是任何一个已提交时刻的状态。
对于 InnoDB,事务型一致性通常依赖一致性读和 MVCC;对于 MyISAM 等非事务引擎,不能仅靠 --single-transaction 获得同样的保证。
2. Binlog、Redo 和 Undo 的职责不同
这三个概念经常被混淆:
| 组件 | 主要内容 | 主要用途 |
|---|---|---|
| InnoDB Redo Log | 数据页修改对应的重做信息 | 崩溃恢复、保证已提交修改能够重新应用 |
| InnoDB Undo Log | 旧版本和回滚信息 | 事务回滚、MVCC 一致性读 |
| MySQL Binlog | 逻辑层的事件或事务变化 | 复制、审计式变更记录、PITR |
一次事务提交的大致关系是:
- 事务修改 InnoDB 页,同时产生 Undo 和 Redo;
- 提交时,MySQL 将事务的 Binlog 写入 Binlog 缓冲区并落盘;
- InnoDB 的提交状态与 Redo 持久化配合完成;
- 事务对其他会话可见;
- 后续崩溃恢复使用 Redo 恢复数据页,PITR 使用 Binlog 在备份之上重放逻辑变化。
Redo 不是长期备份。它的生命周期受日志空间和检查点控制,旧 Redo 会被循环复用。Undo 也不是用于重建整个数据库的增量日志。要实现跨越较长时间的恢复,必须保存全量备份和连续 Binlog。
二、逻辑备份:把数据库导出为可执行内容
1. 逻辑备份的含义
逻辑备份保存的是数据库对象和数据的逻辑表示,例如:
CREATE TABLE ...
INSERT INTO ...
CREATE VIEW ...
CREATE PROCEDURE ...
恢复时,MySQL 客户端重新执行这些 SQL,重新创建表并插入数据。
常见工具包括:
mysqldump:单实例、单库或单表逻辑导出;- MySQL Shell 的
util.dumpInstance()、util.dumpSchemas():支持并行导出和更适合大规模逻辑迁移的格式; mysql:执行 SQL 导出文件;mysqlbinlog:读取或重放 Binlog,不是全量逻辑备份工具。
逻辑备份与物理备份的根本差别是:逻辑备份保存“如何表示数据”,物理备份保存“数据页和相关存储文件”。
2. 一个可执行的 InnoDB 逻辑备份示例
先准备测试数据:
CREATE DATABASE IF NOT EXISTS shop
CHARACTER SET utf8mb4
COLLATE utf8mb4_0900_ai_ci;
USE shop;
CREATE TABLE account (
id BIGINT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
balance DECIMAL(12, 2) NOT NULL
) ENGINE = InnoDB;
INSERT INTO account(id, name, balance)
VALUES
(1, 'Alice', 1000.00),
(2, 'Bob', 500.00);
CREATE TABLE order_info (
id BIGINT PRIMARY KEY,
account_id BIGINT NOT NULL,
amount DECIMAL(12, 2) NOT NULL,
created_at TIMESTAMP NOT NULL
) ENGINE = InnoDB;
导出一个数据库:
mysqldump \
--user=backup \
--password \
--host=127.0.0.1 \
--single-transaction \
--routines \
--events \
--triggers \
--databases shop \
> shop.sql
参数含义:
--single-transaction:对事务型表使用一致性读,通常不需要长时间锁表;--routines:导出存储过程和函数;--events:导出 Event Scheduler 事件;--triggers:导出触发器;--databases shop:在文件中包含CREATE DATABASE和USE语句;--password:交互式输入密码,避免密码直接出现在 shell 历史中。
查看导出文件的开头:
head -n 30 shop.sql
通常可以看到类似以下内容:
-- MySQL dump ...
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS=0;
CREATE DATABASE ...
USE `shop`;
CREATE TABLE `account` (...);
实际输出会随版本、选项和对象内容变化,不应依赖某一行注释的固定格式。
恢复到一个已经创建好的目标实例:
mysql \
--user=restore \
--password \
--host=127.0.0.1 \
< shop.sql
如果导出文件包含 --databases shop 生成的建库语句,目标端不需要提前手工创建 shop。
3. --single-transaction 的真正边界
--single-transaction 并不是“对所有表自动实现完全无锁一致性”。
对于 InnoDB,典型过程是:
- 导出程序建立连接;
- 设置适合一致性读取的事务隔离行为;
- 开始事务并建立一致性视图;
- 后续查询按照这个视图读取已提交数据;
- 其他事务通常可以继续修改 InnoDB 表。
因此,导出过程中提交的新事务通常不会出现在这份快照中,但快照中的事务不会因为后续修改而改变。
然而有几个重要边界:
非事务表
MyISAM 表不支持 InnoDB 的 MVCC。一份导出可能读到不一致的内容。若数据库中存在非事务表,需要考虑:
- 业务停写;
- 锁表;
- 使用全局读锁;
- 将非事务表单独处理;
- 或迁移到事务型引擎。
DDL
在导出期间执行 ALTER TABLE、DROP TABLE、TRUNCATE TABLE 等 DDL,可能与导出产生锁冲突、元数据锁等待,或者导致导出结果与预期不一致。
--single-transaction 不能把并发 DDL 变成事务型快照。
长事务和历史版本
导出事务持续时间较长时,InnoDB 需要保留旧版本以服务一致性读。大量写入可能导致 Undo 增长、历史版本堆积,甚至引发性能和空间问题。
可以观察长事务:
SELECT
trx_id,
trx_started,
trx_state,
trx_mysql_thread_id,
trx_query
FROM information_schema.innodb_trx
ORDER BY trx_started;
外键和对象依赖
导出文件通常会临时关闭外键检查,以便按照导出顺序重建对象。恢复时如果数据本身已经不满足外键约束,不能因为导入成功就认为数据正确。恢复完成后应重新验证约束和业务不变量。
4. 导出 Binlog 坐标:让逻辑备份可以用于 PITR
如果逻辑备份要作为 PITR 的全量起点,必须知道备份一致性点对应的 Binlog 位置。
常见示例:
mysqldump \
--user=backup \
--password \
--host=127.0.0.1 \
--single-transaction \
--source-data=2 \
--routines \
--events \
--triggers \
--all-databases \
> full.sql
--source-data=2 会把复制源的 Binlog 文件和位置以注释形式写入导出文件,而不是在恢复时直接执行。这样可以查看:
grep -E "CHANGE REPLICATION SOURCE TO|CHANGE MASTER TO" full.sql
不同版本和兼容模式下,注释中的语句名称可能不同。关键不是语句文本,而是其中的 Binlog 文件和位置。
这里的“位置”必须对应导出的一致性点,而不是随意在导出完成后执行一次:
SHOW BINARY LOG STATUS;
后者得到的可能已经是更晚的位置。使用 --source-data 的价值,就是让工具在建立备份一致性范围时同时记录对应的日志坐标。
执行该选项通常需要读取源状态和执行一致性协调所需的权限。生产环境应使用具有最小必要权限的备份账号,并先在测试实例验证权限组合。
三、物理备份:保存数据文件和存储状态
1. 物理备份的含义
物理备份保存数据库的底层文件,例如:
- InnoDB 表空间;
- 系统表空间;
- 独立表空间;
- Redo Log;
- Undo 相关文件;
- 数据字典相关内容;
- 实例配置中需要配套恢复的文件;
- Binlog 文件。
恢复时通常不是重新执行每条 INSERT,而是将文件放回兼容的 MySQL 数据目录,随后由 InnoDB 进行必要的崩溃恢复或备份恢复处理。
物理备份通常具有以下特征:
- 对大数据量恢复更快;
- 更依赖版本、平台、文件布局和存储引擎;
- 不天然适合跨版本迁移;
- 不能只复制几个
.ibd文件就宣称完成了实例备份。
2. 为什么不能直接复制运行中的数据目录
假设直接执行:
cp -a /var/lib/mysql /backup/mysql
如果 MySQL 正在运行,这个目录可能处于以下状态:
- 某些数据页已经写入,相关 Redo 尚未复制;
- 某些 Redo 已复制,数据页尚未复制;
- 表空间文件之间的复制时间不同;
- 文件复制期间发生页修改;
- Binlog 文件只复制了前半部分;
- 数据字典、表空间和表文件不匹配。
这不是一个受 MySQL 语义保证的热备份。
停机后复制属于冷物理备份。示例:
sudo systemctl stop mysqld
sudo rsync -aHAX --numeric-ids \
/var/lib/mysql/ \
/backup/mysql-full/
sudo systemctl start mysqld
这里的关键不是 rsync,而是 MySQL 已经停止,文件不再变化。恢复时还需要确认:
- 目标 MySQL 版本兼容;
- 文件权限和所有者正确;
- 配置中的
datadir、端口、字符集和插件环境匹配; - 数据目录不是被另一个运行中的实例同时使用;
- Binlog 文件和索引文件完整。
3. 热物理备份工具和存储快照
生产环境常见三类方案:
MySQL Enterprise Backup
这是 MySQL 官方商业备份工具,支持 InnoDB 热备份,并处理在线备份期间产生的变化。具体功能和授权取决于产品版本。
Percona XtraBackup
这是常见的第三方 InnoDB 物理热备工具,不属于 MySQL Server 本身。使用时必须按其支持的 MySQL 版本、Redo 格式和工具版本选择,不能将其语义直接等同于 MySQL 官方工具。
文件系统或云盘快照
存储快照是否可恢复,取决于快照语义:
- 只是单卷崩溃一致性,还是多卷原子一致性;
- 是否同时覆盖数据目录和 Binlog;
- 快照前是否暂停写入;
- 是否需要依赖 Redo 完成恢复;
- 云盘快照是否保证块级快照的一致性。
“云盘有快照”不自动意味着“数据库有可验证的备份”。
4. Clone 的边界
MySQL Clone 是用于克隆 MySQL 数据实例的能力,适合初始化副本、搭建新实例或某些集群场景。它不等同于长期归档备份:
- Clone 通常复制当前实例状态;
- 它不能替代长期保留的全量备份;
- 不能单独提供任意历史时间点恢复;
- 仍需要考虑版本、权限、网络和空间要求。
要实现 PITR,仍然需要全量基线和连续 Binlog。
四、Binlog:PITR 的增量变化来源
1. Binlog 记录什么
Binlog 位于 MySQL Server 层,记录会影响数据或数据库状态的事件。记录格式主要有:
STATEMENT:记录 SQL 语句;ROW:记录行变化;MIXED:由服务器在语句和行格式之间选择。
生产环境通常优先使用 ROW,因为某些非确定性语句在语句格式下可能在不同环境得到不同结果。但 ROW 也会带来事件体积增大、显示内容不直观等代价。
查看当前配置:
SHOW VARIABLES LIKE 'log_bin';
SHOW VARIABLES LIKE 'binlog_format';
SHOW VARIABLES LIKE 'binlog_row_image';
SHOW VARIABLES LIKE 'gtid_mode';
SHOW VARIABLES LIKE 'enforce_gtid_consistency';
修改 Binlog 相关配置通常需要重启或按变量支持情况动态调整,不能仅凭 SET GLOBAL 认为配置已永久生效。
2. 查看当前 Binlog 文件和位置
SHOW BINARY LOG STATUS;
典型结果包含:
File Position
binlog.000123 456789
含义是当前 Binlog 文件和写入位置。位置是字节偏移,不是事务 ID,也不是时间戳。
查看 Binlog 文件:
mysqlbinlog \
--base64-output=DECODE-ROWS \
-vv \
/var/lib/mysql/binlog.000123 \
| less
-v、-vv:尝试将行事件显示为更易读的形式;--base64-output=DECODE-ROWS:配合详细模式显示行事件;- 这只是查看方式,不改变 Binlog 本身。
如果启用 Binlog 校验和,mysqlbinlog 会验证事件内容。遇到损坏时,读取可能报错,不能把截断文件直接当作完整日志。
3. Binlog 保留策略决定 PITR 窗口
PITR 依赖从全量备份时刻开始的连续 Binlog。如果备份完成后 Binlog 很快被清理,那么备份即使完整,也无法恢复到清理范围内的时间点。
需要同时管理:
- Binlog 自动过期时间;
- 外部归档;
- Binlog 文件是否已上传成功;
- 上传校验;
- 本地清理顺序;
- 备份保留周期。
例如,MySQL 8.4 中可以查看:
SHOW VARIABLES LIKE 'binlog_expire_logs_seconds';
不要简单地把保留时间设置为“备份周期”。如果全量备份每周一次,而某次全量备份失败,至少需要保留上一份成功全量备份之后的全部 Binlog,并留出检测和恢复时间。
4. 持久化参数影响已提交事务的可恢复性
常见配置:
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';
SHOW VARIABLES LIKE 'sync_binlog';
典型的强持久化配置是:
innodb_flush_log_at_trx_commit = 1
sync_binlog = 1
直觉上:
innodb_flush_log_at_trx_commit=1:每次事务提交都将 InnoDB Redo 刷到持久存储;sync_binlog=1:每次事务提交相关的 Binlog 都同步到持久存储。
这有助于降低主机崩溃后的丢失窗口,但不是对所有故障的绝对保证。磁盘控制器、电源保护、虚拟化存储和文件系统仍可能影响持久性。
如果为了性能放宽这些配置,必须明确 RPO:主机突然断电时,最近若干已返回成功的事务可能尚未持久化,Redo 和 Binlog 之间也可能出现更复杂的恢复边界。
五、PITR 的完整条件和故障边界
设全量备份记录的 Binlog 起点为:
binlog.000123:456789
目标时间为:
2025-03-08 14:35:00
理想恢复流程是:
1. 必须有“全量状态 + 连续日志”
以下组合不能完成可靠 PITR:
| 现状 | 问题 |
|---|---|
| 只有全量备份,没有 Binlog | 只能恢复到全量备份时刻 |
| 只有 Binlog,没有全量备份 | 缺少重放起点 |
| Binlog 中间缺文件 | 后续事件无法形成完整历史 |
| 只保留部分表的逻辑备份,却重放全实例 Binlog | 可能缺少其他表和对象 |
| 备份坐标不准确 | 可能漏放或重复放事务 |
| 恢复到错误的时间边界 | 误删或误更新仍然被重放 |
2. 时间点不是足够精确的唯一标识
Binlog 事件包含时间信息,但时间戳通常只能精确到秒,而且事件时间可能受事务提交和记录方式影响。多个事务可以具有相同时间戳。
因此:
--stop-datetime适合粗粒度恢复;- 对误操作恢复,最好根据 Binlog 事件、事务边界和位置确定停止点;
- 目标时间必须转换成“某个事务之前”或“某个事务之后”,而不是只凭应用日志中的秒级时间。
3. GTID 与文件位置
GTID 为事务提供逻辑标识,例如:
source_uuid:12345
GTID 集合可以表达一组已执行或需要执行的事务,比单纯文件位置更适合复制拓扑变化和缺少连续文件编号的情况。
查看 GTID 配置:
SHOW VARIABLES LIKE 'gtid_mode';
SHOW VARIABLES LIKE 'enforce_gtid_consistency';
SELECT @@GLOBAL.gtid_executed;
但 GTID 不会自动解决所有恢复问题:
- 目标实例可能已经执行过部分 GTID;
- 目标实例可能带有不应存在的
gtid_executed状态; - 恢复到新实例和恢复到原实例的处理方式不同;
- 使用
--skip-gtids或手工修改 GTID 相关行为可能导致重复执行或错误跳过。
文件位置适合明确的单实例 Binlog 重放;GTID 适合需要识别事务集合的环境。实际选择应与备份工具、复制拓扑和恢复目标保持一致。
六、PITR 演练:从全量备份恢复到误操作前
下面演练一个典型场景:
- 先做一份
shop数据库全量逻辑备份; - 之后插入一笔订单;
- 随后执行错误的删除;
- 恢复到删除之前。
1. 演练前提
检查:
SHOW VARIABLES LIKE 'log_bin';
SHOW VARIABLES LIKE 'binlog_format';
SHOW BINARY LOG STATUS;
要求:
log_bin=ON;- 备份账号具备导出所需权限;
shop主要使用 InnoDB;- 备份文件和从备份点开始的 Binlog 都可读取;
- 恢复目标最好是隔离的新实例,不要直接覆盖生产实例。
2. 做带坐标的全量备份
mysqldump \
--user=backup \
--password \
--host=127.0.0.1 \
--single-transaction \
--source-data=2 \
--routines \
--events \
--triggers \
--databases shop \
> shop-full.sql
保存导出文件后,记录其摘要:
sha256sum shop-full.sql > shop-full.sql.sha256
同时保存:
- 执行时间;
- MySQL 版本;
- 导出命令;
SHOW BINARY LOG STATUS或--source-data中的坐标;- 账号和权限说明;
- 文件校验值。
3. 产生一笔正常事务
USE shop;
START TRANSACTION;
INSERT INTO order_info(id, account_id, amount, created_at)
VALUES (1001, 1, 88.00, CURRENT_TIMESTAMP);
UPDATE account
SET balance = balance - 88.00
WHERE id = 1;
COMMIT;
由于使用了 InnoDB,这两个修改作为一个事务提交。以行格式 Binlog 为例,Binlog 中会出现事务开始、行变化和提交相关事件。
可以在当前日志文件中查找该事务:
mysqlbinlog \
--base64-output=DECODE-ROWS \
-vv \
/var/lib/mysql/binlog.000123 \
| grep -n -A 30 -B 10 "1001"
实际生产环境不应仅用 grep 作为解析器。更可靠的做法是查看完整事务,确认:
- 事务的 GTID(如果启用);
- 事务开始位置;
- 行事件;
COMMIT;- 下一个事务的起始位置。
4. 产生错误操作
例如应用错误地执行:
USE shop;
DELETE FROM order_info
WHERE account_id = 1;
如果没有 WHERE 限制到正确订单,可能删除了账户 1 的全部订单。
此时不要继续在原库尝试“反向写 SQL”修复。反向 SQL 可能缺少被删除行的完整原值,也可能与并发写入互相覆盖。正确方式是使用备份和 Binlog 重建隔离恢复实例。
5. 准备恢复实例
恢复实例应满足:
- MySQL 版本与备份来源兼容;
- 数据目录为空或为专用实例;
- 不接收业务流量;
- 应用账号不能连接;
- 如不希望恢复过程继续写入 Binlog,应按恢复环境的权限和拓扑设计处理。
导入全量备份:
mysql \
--user=root \
--password \
--host=127.0.0.1 \
< shop-full.sql
导入后检查:
SELECT COUNT(*) FROM shop.order_info;
SELECT * FROM shop.account ORDER BY id;
此时状态应该是全量备份时刻的状态:已经提交到备份快照的内容存在,备份开始或结束之后才发生的事务不一定存在。
6. 从备份坐标开始读取 Binlog
如果备份文件中的坐标是:
binlog.000123:456789
可以先导出从该位置开始的 SQL 文本进行审查:
mysqlbinlog \
--start-position=456789 \
/var/lib/mysql/binlog.000123 \
> replay.sql
如果后续已经切换到多个文件,需要按顺序读取:
mysqlbinlog \
--start-position=456789 \
/var/lib/mysql/binlog.000123 \
/var/lib/mysql/binlog.000124 \
/var/lib/mysql/binlog.000125 \
> replay.sql
注意文件必须按 Binlog 顺序排列,不能按文件修改时间、目录显示顺序或上传完成顺序随意拼接。
审查 replay.sql 时,应定位错误 DELETE 所在事务。目标是:
重放备份点之后的正常事务
但不重放错误 DELETE 事务及其之后不应恢复的内容
7. 按事务边界停止重放
如果已经确定错误事务开始位置为 P_bad,可以把停止位置设为该事务开始位置之前的边界。实际使用时,必须根据 mysqlbinlog 输出中的事件位置确认语义,不能直接把“看到的任意 end_log_pos”当作停止位置。
常见做法是先生成一个不包含错误事务的文件,例如:
mysqlbinlog \
--start-position=456789 \
--stop-position=789012 \
/var/lib/mysql/binlog.000123 \
/var/lib/mysql/binlog.000124 \
> replay-before-delete.sql
这里的 789012 不是固定值,而应当是经过 Binlog 检查后确定的边界:它应位于错误事务开始之前,并且不要截断前一个事务。生产操作记录中必须保存“为什么选择这个位置”的证据,例如事务 GTID、事务起止位置和人工复核结果。
随后导入:
mysql \
--user=root \
--password \
--host=127.0.0.1 \
< replay-before-delete.sql
如果使用 --stop-datetime:
mysqlbinlog \
--start-position=456789 \
--stop-datetime='2025-03-08 14:35:00' \
/var/lib/mysql/binlog.000123 \
/var/lib/mysql/binlog.000124 \
| mysql --user=root --password --host=127.0.0.1
这个方式只能在目标时间和 Binlog 时间语义足够明确时使用。对同一秒内存在多个事务的情况,位置或 GTID 通常更可靠。
8. 恢复后的验证
恢复完成不能只看命令退出码为 0。至少检查:
SELECT COUNT(*) FROM shop.order_info;
SELECT
id,
balance
FROM shop.account
ORDER BY id;
SELECT
SUM(amount) AS total_amount
FROM shop.order_info
WHERE account_id = 1;
还应验证:
- 被误删的订单存在;
- 错误删除之后不应恢复的事务没有被重放;
- 账户余额与订单业务规则一致;
- 外键、唯一键和非空约束没有异常;
- 存储过程、触发器、事件和视图存在;
- 字符集、排序规则和时区符合原实例;
- 应用以只读方式执行关键查询;
- 必要时对源库和恢复库做按主键范围的校验或抽样校验。
七、如何从远程服务器读取和归档 Binlog
Binlog 不应只留在数据库主机本地。磁盘损坏、误删文件、主机丢失都可能同时摧毁数据和 Binlog。
查看远程 Binlog 可以使用:
mysqlbinlog \
--read-from-remote-server \
--host=db.example.com \
--port=3306 \
--user=binlog_reader \
--password \
--raw \
--result-file=/backup/binlog/ \
binlog.000123
这里的重点是:
--read-from-remote-server:从远程 MySQL 读取;--raw:保存原始 Binlog 文件,而不是转换为 SQL 文本;--result-file:指定保存目录;- 账号需要读取 Binlog 所需权限;
- 归档完成后应校验文件大小、校验和和文件序列。
生产归档通常还需要处理:
- 新文件不断生成;
- 归档端重启后从断点继续;
- 文件上传成功后再允许源端清理;
- 源端已经清理但归档不完整时报警;
- 归档文件加密和访问控制;
- 防止归档文件被篡改。
Binlog 中可能包含业务敏感数据,尤其是行事件解码后可能直接暴露列值,因此归档权限必须按敏感数据处理。
八、物理恢复与逻辑恢复的取舍
| 维度 | 逻辑备份 | 物理备份 |
|---|---|---|
| 保存内容 | SQL、对象和逻辑数据 | 表空间、日志和实例文件 |
| 恢复速度 | 数据量大时通常较慢 | 通常更快 |
| 跨版本迁移 | 通常更灵活 | 兼容约束更强 |
| 跨平台 | 通常较容易 | 受文件格式和环境影响 |
| 单表恢复 | 方便 | 通常需要额外流程 |
| 可读性 | 人工可审查 | 不适合直接阅读 |
| PITR 配合 | 需要准确记录 Binlog 坐标 | 需要保存物理备份一致性元数据 |
| 对非 InnoDB 的处理 | 需要额外锁和一致性设计 | 需要确认引擎文件是否被正确纳入 |
| 空间和速度 | 可能占用更多 CPU 和时间 | 通常更适合大实例 |
这不是“逻辑一定好”或“物理一定好”的选择。
常见组合是:
- 使用物理热备作为快速灾难恢复基线;
- 使用逻辑备份作为跨版本迁移、单库恢复和对象审查手段;
- 两者都保留 Binlog,以提供 PITR;
- 定期把备份恢复到隔离实例进行验证。
九、常见误解和失败表现
误解一:mysqldump 命令成功就说明备份一致
不一定。需要继续确认:
- 是否中途连接断开;
- 输出文件是否被截断;
- 是否包含需要的 routines、events 和 triggers;
- 是否存在非事务表;
- 是否发生了并发 DDL;
- 是否记录了正确的 Binlog 坐标;
- 恢复测试是否成功。
检查文件末尾:
tail -n 20 shop-full.sql
导出文件通常会有结束标记或恢复状态语句,但不能只依赖某个固定文本判断成功。更可靠的是查看工具退出码、文件大小变化、日志和实际导入结果。
误解二:复制 .ibd 文件就是物理备份
独立表空间并不是孤立数据库。表空间 ID、数据字典、系统表空间、Redo、Undo、配置和表定义可能共同决定其可用性。脱离完整备份体系复制单个 .ibd 文件,通常不能直接恢复整张表。
误解三:Binlog 就是数据库的完整历史快照
Binlog 只记录发生过的变化,不包含全量状态。它依赖一个正确的基线,而且 Binlog 可能受格式、过滤规则、DDL、非确定性语句和保留周期影响。
误解四:用时间戳恢复到某秒就一定准确
多个事务可能共享同一秒的时间戳。应用日志时间、数据库服务器时间、客户端时间和 Binlog 事件时间也可能不同。精确恢复应结合事务内容、位置和 GTID,必要时先在隔离环境演练多次。
误解五:恢复进程返回成功就表示恢复正确
恢复程序成功只说明输入事件被接受。它不说明:
- 恢复到了正确时间点;
- 应用数据没有被误过滤;
- 触发器和事件符合预期;
- 业务约束仍然成立;
- 所有 Binlog 文件都已覆盖。
恢复验证必须包含数据库层检查和业务层检查。
十、恢复失败时的诊断路径
1. 逻辑导入报错
常见错误包括:
Unknown collation:目标版本不支持源端排序规则;Table already exists:目标实例不是空实例,或恢复顺序不正确;Access denied:账号缺少创建对象、触发器、事件或例程权限;Duplicate entry:重复导入或恢复边界错误;- 外键错误:对象顺序、数据完整性或关闭外键检查后的数据问题;
- 字符集错误:客户端字符集、连接字符集和表定义不一致。
诊断命令:
mysql --version
mysqldump --version
目标端检查:
SELECT VERSION();
SHOW VARIABLES LIKE 'character_set%';
SHOW VARIABLES LIKE 'collation%';
SHOW PLUGINS;
2. Binlog 重放报错
重点区分:
- 文件找不到:归档不完整或路径错误;
Event read failed:Binlog 截断或损坏;Duplicate entry:恢复起点重复,或目标库并非干净基线;Unknown database:全量备份没有包含对应数据库,或 Binlog 中存在该库的变化;- GTID 已执行:目标实例的 GTID 状态与恢复计划不匹配;
- 语句依赖外部状态:语句格式 Binlog 可能依赖原实例变量、当前数据库、临时表或非确定性函数。
每次 PITR 都应保存:
- 全量备份文件校验值;
- 全量备份对应的 Binlog 文件和位置;
- 使用过的 Binlog 文件列表;
mysqlbinlog参数;- 停止位置或目标 GTID;
- 失败时的错误行和目标实例日志;
- 恢复前后关键数据校验结果。
3. 物理恢复无法启动
常见原因:
- 直接复制运行中数据目录导致文件不一致;
- 目标 MySQL 版本不兼容;
- 文件所有者和权限错误;
- 配置的
datadir与实际目录不同; - 只复制了部分表空间;
- 缺少系统表空间、Redo、Undo 或相关元数据;
- 恢复实例与原实例使用了不同的插件或加密密钥;
- 加密表空间缺少密钥管理配置。
首先查看错误日志:
journalctl -u mysqld --no-pager -n 200
不要在原始备份目录上反复尝试启动。应复制出工作副本,保留原始证据,避免一次失败操作破坏后续恢复可能性。
十一、恢复设计中的 RPO、RTO 和验证闭环
两个指标需要先区分:
- RPO(Recovery Point Objective):最多能接受丢失多长时间的数据;
- RTO(Recovery Time Objective):最多能接受恢复花费多长时间。
举例:
- 每周一次全量逻辑备份,Binlog 只保留三天:无法保证七天内任意时点恢复;
- 每天一次物理备份、Binlog 实时归档:通常可以把恢复点推进到备份后的某个 Binlog 事务;
- 只有备份没有定期恢复演练:RTO 实际上是未知的。
一个可验证的闭环应当是:
- 生成全量备份;
- 记录一致性点和 Binlog 坐标;
- 归档连续 Binlog;
- 校验备份文件和日志文件;
- 在隔离实例恢复;
- 按目标时间重放;
- 验证数据库和业务数据;
- 测量实际恢复耗时;
- 记录缺陷并修正备份流程。
尤其要验证以下故障路径:
- 误删一张表;
- 误更新大量行;
- 主机磁盘损坏;
- 全量备份文件损坏;
- 中间 Binlog 文件缺失;
- 主实例不可用但远程 Binlog 仍可获取;
- 恢复后需要将业务切换到新实例;
- 恢复完成后如何重新建立复制或重新开始 Binlog 归档。
十二、一个可落地的备份恢复组合
对于主要使用 InnoDB 的单实例 MySQL,可以采用如下结构:
定期全量备份
├── 逻辑备份:用于迁移、单库恢复、对象审查
└── 物理备份:用于大实例快速恢复
持续 Binlog 归档
└── 覆盖最近一次成功全量备份之后的全部日志
恢复流程
├── 选择全量备份
├── 验证备份校验值
├── 恢复到隔离实例
├── 从一致性坐标开始读取 Binlog
├── 按事务边界停止
├── 执行数据库和业务校验
└── 决定数据导出、实例切换或重新建立复制
其中最容易被忽略的是“从一致性坐标开始”。如果起点过早,可能重复执行事务;如果起点过晚,可能丢失事务。使用逻辑备份时,应让备份工具记录坐标;使用物理备份时,应保存备份工具生成的元数据,而不是凭人工记忆执行 SHOW BINARY LOG STATUS。
备份恢复系统最终要证明的不是“文件存在”,而是:
只有这四部分同时成立,逻辑备份、物理备份和 Binlog 才真正构成了可执行的 PITR 能力。
系列导航与关联阅读
- 系列入口:数据库完整学习路线:从关系模型、事务索引到分布式与向量检索
- 上一篇:MySQL SQL 实战:分页、批量、Upsert、JSON、窗口函数和锁定读
- 下一篇:MySQL 用户、角色与安全:认证插件、权限、TLS、审计和密钥
- 延伸:MySQL Redo、Undo 与 Binlog:提交链路、崩溃恢复和一致性
- 延伸:MySQL 升级与迁移:兼容检查、DDL、字符集、灰度和回滚
官方资料
本文依据数据库官方文档重新梳理;正文、示例与生产检查清单由 WR BLOG 编写。

评论
0 条讨论