大数据技术

MySQL:增量备份做了三次、恢复只到第一次,XtraBackup按LSN页级变更、必须全备→增量1→增量2链式prepare,顺序错一步后面全废?

👤 为我痴狂 👁 28 阅读 ❤ 0 点赞 ➦ 0 分享 📅 2026-11-02
首页› 大数据› 大数据技术› 正文
MySQL:增量备份做了三次、恢复只到第一次,XtraBackup按LSN页级变更、必须全备→增量1→增量2链式prepare,顺序错一步后面全废?

一次“备份成功、恢复却停在第一次增量”的现场复盘:LSN 页级变更如何被追踪,链式 prepare 为何不可跳步,以及如何把这条脆弱的恢复链改造成可验证、可巡检的工程资产。

摘要与问题定义

某业务库按“每周一次全量 + 每日一次增量”的策略运行,连续三天生成了三份增量备份,备份日志全部显示 completed OK。等到真正需要恢复时,DBA 按时间顺序把全备与三份增量依次 apply,最终启动的实例却只反映第一次增量结束时的数据状态,后两次增量的数据完全缺失。更棘手的是,恢复过程没有报错,只有若干条被忽略的 warning,直到业务方核对数据才发现问题。

这不是 XtraBackup 的“bug”,而是其基于 LSN 的页级增量模型与链式 prepare 语义共同决定的必然结果。要理解“为什么只到第一次”,必须先回答三个问题:增量备份到底记录了什么;prepare 阶段在做什么;恢复链的顺序约束从何而来。本文的主线是:XtraBackup 的增量链本质上是一条以 LSN 为序的偏序约束链,任何跳步、错序或元数据断裂都会让链在断点处“静默截断”,而不会显式失败。围绕这条主线,本文从机制、复现、诊断、工程化四个层面展开。

核心判断:“顺序错一步后面全废”这句话在工程上并不精确。更准确的表述是——顺序错一步,后续增量在 prepare 时会被判定为“不满足前序 LSN 边界”而被跳过或产生不一致的页版本,最终表现为静默的数据截断。真正危险的不是报错,而是不报错。

目录

一、背景:从逻辑备份到物理增量的范式转移

1.1 逻辑备份的边界

mysqldump 与 mydumper 属于逻辑备份,输出的是 SQL 语句或表数据文本。其优势是跨版本、跨平台、可读性强;劣势在大库场景下非常明显:备份与恢复都是单线程或有限并行的 SQL 重放,恢复时间随数据量线性甚至超线性增长。对于 TB 级实例,一次完整恢复动辄数小时,RTO 无法接受。逻辑备份的另一个隐性成本是一致性快照的获取窗口——为保证一致性,通常需要开启事务并持有一致性读视图,长事务会推高 undo 保留量。

物理备份直接复制数据文件页,恢复时无需 SQL 重放,只需做崩溃恢复(redo apply + undo rollback)。XtraBackup 属于物理热备工具,其增量能力正是建立在“页”这一物理粒度上。本文评述:物理备份把恢复时间从“与逻辑数据量成正比”压缩为“与 redo 量成正比”,这是 RTO 敏感场景的决定性优势;但代价是把一致性责任从 SQL 层下移到存储引擎层,运维者必须理解 InnoDB 的 LSN 体系,否则就会在增量链上踩坑。

1.2 增量备份的动机

全量备份的成本随数据量增长,而日变更量往往只占全量的很小比例。以某电商订单库为例,全量 2.4 TB,日增页变更约 30–60 GB(模拟数据,基于典型 OLTP 写放大比例估算)。若每天全备,备份窗口与存储成本都难以承受;若采用“周全备 + 日增量”,备份窗口可压缩到原来的 1/5 左右,存储占用也显著下降。这正是 XtraBackup 增量备份的设计初衷。

但增量带来一个新的工程问题:恢复不再是单点操作,而是一条链。链上的每一环都依赖前一环的终态。链越长,断裂概率越高;而断裂往往是静默的。这一点与数据库 binlog 的“全备 + binlog 增量”模型有本质区别——后者由 binlog 位点(position / GTID)显式串联,错位会直接报错;前者由 LSN 隐式串联,错位可能被“容忍”成一次不完整的 apply。

二、原理:LSN、页级变更与增量备份的元数据契约

2.1 LSN 的定义与单调性

LSN(Log Sequence Number)是 InnoDB 中 redo 日志的字节偏移量,是一个单调递增的 64 位整数。每一次页修改都会在 redo 中追加记录,LSN 随之增长。InnoDB 在页头(FIL_PAGE_LSN)记录该页最后一次被修改时的 LSN。这个设计是增量备份的物理基础:只要比较页头 LSN 与某个基准 LSN,就能判断该页在基准之后是否被修改过。

需要区分几个易混概念:log sequence number(当前 redo 写入位置)、log flushed up to(已刷盘的 redo 位置)、pages flushed up to(已刷盘的数据页对应的 LSN)、last checkpoint at(最近一次检查点 LSN)。增量备份的基准取的是备份开始时刻的一致性 LSN,而非 checkpoint LSN,这一点在诊断时极易混淆。

2.2 页级变更追踪的实现

XtraBackup 的增量备份并非读取 redo 日志来“重放”变更,而是扫描数据文件,逐页比较页头 LSN 与基准 LSN,把 LSN 大于基准的页复制出来。这带来两个重要推论:

  • 增量备份的大小取决于“被修改过的页数”,而非“逻辑变更量”。一次大范围 UPDATE 可能只改少量页,而一次全表扫描型 DDL 可能改动大量页。
  • 增量备份不包含 redo 的完整历史,它只包含“页的当前版本”。因此恢复时必须依赖全备中的 redo 与增量中的页版本共同完成一致性构建。

每个增量备份目录下都会生成 xtrabackup_checkpoints 文件,其中关键字段包括 from_lsn、to_lsn、last_lsn、backup_type。它们构成增量链的“元数据契约”:

backup_type = incremental
from_lsn = 12345678
to_lsn   = 12456789
last_lsn = 12456800
compact  = 0

本文评述:from_lsn 与 to_lsn 的关系是判断链是否连续的唯一可靠依据。很多“只恢复到第一次”的案例,根因就是某一份增量的 from_lsn 与前一份的 to_lsn 不相等,而恢复脚本没有做这个校验。

2.3 增量备份的两种模式

XtraBackup 8.0 系列支持基于 LSN 的页级增量,也支持基于 redo 归档的增量思路(后者更多体现在 Percona XtraBackup 与 MySQL Enterprise Backup 的差异中)。页级增量的优点是实现简单、不依赖 redo 归档配置;缺点是增量大小对“页修改分布”敏感,且无法表达“页内部分变更”。

维度 页级增量(LSN) redo 归档增量
追踪粒度 数据页 redo 记录
依赖配置 无需额外配置 需开启 redo 归档
增量体积 受页修改分布影响 接近逻辑变更量
恢复复杂度 链式 prepare,顺序敏感 redo 重放,顺序由 LSN 保证

笔者认为,页级增量的“顺序敏感”并非设计缺陷,而是把一致性责任显式交给了运维流程。它换来了实现上的简洁与对存储引擎内部结构的低侵入性。理解这一点,才能接受“必须链式 prepare”这一约束,而不是试图绕过它。

三、链式 prepare:为什么必须全备→增量1→增量2

3.1 prepare 阶段到底在做什么

备份完成时,数据文件处于“不一致”状态:部分页已刷盘、部分页仍在 buffer pool,redo 中还有未应用的记录。prepare 阶段的核心任务是崩溃恢复——把 redo 中从 checkpoint LSN 到备份结束 LSN 的记录应用到数据页,使数据文件达到事务一致状态,并回滚未提交事务。对全备而言,这是一次标准的 InnoDB 崩溃恢复。

对增量而言,prepare 还多了一层语义:把增量中的页版本合并进前序备份的目录。XtraBackup 的做法是 --apply-log-only 与 --redo-only 的组合使用:在合并中间增量时只应用 redo 而不做 undo rollback,保留未提交事务,以便后续增量继续在其上构建;直到最后一个增量合并完成后,才执行完整的 undo rollback。

# 1) 全备 prepare(可完整回滚)
xtrabackup --prepare --target-dir=/backup/full

# 2) 增量1 合并(只应用 redo,不回滚)
xtrabackup --prepare --apply-log-only \
  --target-dir=/backup/full \
  --incremental-dir=/backup/inc1

# 3) 增量2 合并(同样只应用 redo)
xtrabackup --prepare --apply-log-only \
  --target-dir=/backup/full \
  --incremental-dir=/backup/inc2

# 4) 最后一个增量:完整 prepare(含 undo rollback)
xtrabackup --prepare \
  --target-dir=/backup/full \
  --incremental-dir=/backup/inc3

注意第 4 步去掉了 --apply-log-only。这是整个流程中最容易被忽略的细节:如果最后一步仍带 --apply-log-only,实例可以启动,但会残留未回滚事务,表现为启动后出现大量回滚、甚至数据不一致;如果中间步骤误用了完整 prepare,则未提交事务被提前回滚,后续增量基于错误状态构建,链在语义上已经断裂。

3.2 顺序约束的形式化

设全备为 B0,增量为 B1、B2、B3,各自携带区间 [from_lsn, to_lsn]。链式 prepare 要求对任意 i,Bi.from_lsn = Bi-1.to_lsn(允许极小误差,但必须满足单调不重叠)。合并操作在语义上是函数复合:

Apply(B0, B1, B2, B3) = Merge(Merge(Merge(B0, B1), B2), B3)

Merge 不是可交换的。Merge(B0, B2) 在 B2 的页版本覆盖 B0 时,会丢失 B1 引入的中间页版本;更隐蔽的是,若 B2 中某页未被修改,Merge 会保留 B0 的旧页,而 B1 对该页的修改就此丢失。这就是“跳过 B1 直接合并 B2”会导致数据静默缺失的机制根源。本文评述:把这条约束理解为“偏序链”比“线性顺序”更准确——它要求的是每一环的起点必须精确锚定在前一环的终点,而不是简单的“按时间排序”。

3.3 为什么“错一步”常常不报错

XtraBackup 在合并时主要依据页头 LSN 判断是否覆盖,而不是严格校验 from_lsn 是否等于前序 to_lsn。当顺序错误但 LSN 区间仍有重叠时,合并可能“成功”完成,只是结果不符合预期。只有当 LSN 区间完全不可比时,才会出现明显报错。这种“宽容”是工程上的双刃剑:它容忍了备份时刻的微小 LSN 漂移,却也放过了真正的顺序错误。

因此,不能依赖工具报错来保证链的完整性。必须在流程层面显式校验。这也是本文后续“工程化方案”一节的核心出发点。

四、故障复现:三次增量只恢复到第一次的完整路径

4.1 环境与数据准备

为便于复现,构造一个最小实验:MySQL 8.0.35 + Percona XtraBackup 8.0.35,单实例,库 demo,表 t_ledger。每天插入一批带日期标记的记录,便于事后判断恢复到了哪一天。

CREATE TABLE demo.t_ledger (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  batch_tag VARCHAR(32) NOT NULL,
  amount DECIMAL(18,2) NOT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  KEY idx_tag (batch_tag)
) ENGINE=InnoDB;

-- D1 全备前
INSERT INTO demo.t_ledger(batch_tag, amount) VALUES ('D0', 100.00);
-- D2 增量1 前
INSERT INTO demo.t_ledger(batch_tag, amount) VALUES ('D1', 200.00);
-- D3 增量2 前
INSERT INTO demo.t_ledger(batch_tag, amount) VALUES ('D2', 300.00);
-- D4 增量3 前
INSERT INTO demo.t_ledger(batch_tag, amount) VALUES ('D3', 400.00);

4.2 备份序列与 LSN 记录

按“全备 → 增量1 → 增量2 → 增量3”执行备份,并记录每份备份的 checkpoints 文件。下表为模拟数据(基于上述实验环境的典型 LSN 增量,非真实生产值),用于说明链的形态:

备份 类型 from_lsn to_lsn 链状态
full full 0 1000000 锚点
inc1 incremental 1000000 1200000 连续
inc2 incremental 1200000 1400000 连续
inc3 incremental 1400000 1600000 连续

4.3 错误恢复路径

现场的错误操作有两种典型形态。形态 A:只合并了 inc1 就执行了完整 prepare 并启动,后续 inc2、inc3 根本没有被合并。形态 B:合并顺序为 inc1 → inc3 → inc2,或 inc2 的 from_lsn 因某次备份异常而不等于 inc1 的 to_lsn,导致 inc3 的页版本覆盖了 inc2 的部分修改。

# 形态 A:只合并了 inc1 就完整 prepare
xtrabackup --prepare --target-dir=/backup/full
xtrabackup --prepare --apply-log-only \
  --target-dir=/backup/full --incremental-dir=/backup/inc1
xtrabackup --prepare --target-dir=/backup/full   # 提前完整回滚
# 启动后查询:只有 D0、D1 两批数据,D2、D3 缺失

形态 A 的根因是流程编排错误:把“最后一个增量”的判定写死成了 inc1,而实际链有三段。形态 B 的根因是链校验缺失:没有在合并前比对 from_lsn 与 to_lsn。两种形态的共同表现都是“恢复成功但数据不全”,且日志中没有 fatal 级别错误。

现场教训:“备份成功”与“可恢复”是两个独立命题。备份任务的退出码只说明备份动作完成,不说明恢复链完整。恢复链的完整性必须在备份侧就被验证,而不是等到恢复时才发现。

五、诊断方法:从 xtrabackup_checkpoints 读出真相

5.1 三步定位法

第一步,列出所有备份目录的 checkpoints 文件,按 to_lsn 排序,检查相邻项是否满足 from_lsn[i] == to_lsn[i-1]。第二步,检查恢复脚本中“最后一个增量”的判定逻辑是否与实际链长一致。第三步,检查最终 prepare 是否去掉了 --apply-log-only。

for d in /backup/full /backup/inc1 /backup/inc2 /backup/inc3; do
  echo "== $d"
  grep -E 'backup_type|from_lsn|to_lsn|last_lsn' "$d/xtrabackup_checkpoints"
done

5.2 恢复后的一致性验证

仅看 checkpoints 还不够,必须在恢复后做数据层验证。推荐三类校验:其一,按业务批次标记查询最大批次号,确认恢复到了预期批次;其二,对关键表做行数与校验和比对(如 CHECKSUM TABLE 或基于主键区间的聚合比对);其三,检查 information_schema.innodb_metrics 中与回滚相关的指标,确认没有异常残留。

本文评述:数据层验证是唯一能证伪“恢复成功”的手段。工具层面的成功信号(退出码、日志中的 completed OK)只覆盖了“动作完成”,不覆盖“语义正确”。把数据层验证纳入恢复演练的必选项,是避免静默截断的最后一道防线。

5.3 常见误判

一个常见误判是把 last_lsn 当作链的终点。实际上 last_lsn 通常略大于 to_lsn,用于标记备份结束时刻的 redo 位置,链校验应以 to_lsn 为准。另一个误判是认为“增量越小越健康”,实际上增量过小可能意味着备份期间几乎没有写入,也可能意味着备份读取了错误的基准 LSN,需要结合业务写入量交叉判断。

六、工程化方案:把恢复链变成可验证资产

6.1 备份侧的链校验

在每次增量备份完成后,立即读取其 from_lsn,与上一份备份的 to_lsn 比对。不一致则告警并阻止该增量进入“可用链”。这一步应在备份任务内部完成,而不是依赖人工巡检。可维护一张元数据表记录链状态:

字段 含义 用途
chain_id 备份链标识 关联全备与增量
seq_no 链内序号 保证顺序
from_lsn / to_lsn 区间端点 连续性校验
verified 是否通过校验 恢复时只选 verified 链

6.2 恢复侧的编排模板

恢复脚本不应硬编码增量数量,而应从元数据表读取链,按 seq_no 升序合并,最后一份去掉 --apply-log-only。伪代码如下:

chain = load_verified_chain(chain_id)   # 已按 seq_no 排序
assert chain[0].type == 'full'
for prev, cur in pairwise(chain):
    assert cur.from_lsn == prev.to_lsn, "链断裂"

xtrabackup --prepare --target-dir=full_dir
for item in chain[1:-1]:
    xtrabackup --prepare --apply-log-only \
      --target-dir=full_dir --incremental-dir=item.dir
last = chain[-1]
xtrabackup --prepare --target-dir=full_dir --incremental-dir=last.dir

6.3 定期恢复演练

建议以周为单位,从最近的 verified 链中随机抽取一条,在隔离环境完成“恢复到临时实例 + 数据层校验”的全流程,并记录耗时(RTO 实测值)。演练结果应回写到元数据表,形成“备份—校验—演练”的闭环。本文评述:恢复演练的价值不在于“演练”本身,而在于它把隐性的链断裂风险转化为显性的、可度量的指标。没有演练的备份体系,本质上是在赌链不会断。

6.4 拓展资源

延伸阅读可参考 Percona 官方文档中关于增量备份与 prepare 的章节,以及 MySQL 官方手册中 InnoDB 崩溃恢复与 LSN 的说明。视频类资源可关注 Percona 在 YouTube 上的 XtraBackup 专题分享,以及国内技术社区(如墨天轮、知数堂)关于物理备份恢复演练的公开课。以下链接供拓展(均在新窗口打开):

七、对比与局限:XtraBackup、PXB 与克隆插件

7.1 三种物理备份路径的定位

Percona XtraBackup(PXB)是 XtraBackup 的开源分支,二者在增量与 prepare 语义上高度一致,本文讨论的链式约束同样适用。MySQL 8.0 引入的 Clone Plugin 提供了另一种物理复制路径:它基于页传输协议,在克隆过程中由接收端持续应用 redo,最终达到一致状态。Clone 的优势是无需显式 prepare、无需维护增量链;局限是它面向“实例级克隆”,不天然支持“周全备 + 日增量”的长期保留策略。

能力 XtraBackup / PXB Clone Plugin
增量备份 支持(LSN 页级) 不提供长期增量链
恢复步骤 链式 prepare,顺序敏感 自动一致,步骤少
跨版本 较好 受版本约束
适用场景 长期保留、跨环境迁移 快速扩缩容、主从重建

7.2 局限与边界条件

页级增量的第一个局限是对页修改分布的敏感性:一次大范围但低选择性的 UPDATE 会修改大量页,使增量体积接近全量。第二个局限是无法表达页内部分变更:即使只改一行,也要复制整页(默认 16 KB)。第三个局限是链的脆弱性:链越长,任一环的元数据异常都会导致后续不可用。

笔者认为,这些局限决定了增量链不适合无限延长。工程上应设置“链长上限”(例如 7 段),超过则触发新的全备,形成多条短链。短链的恢复路径更短、校验成本更低、断裂影响面更小。这与日志结构合并树(LSM-Tree)中“定期 compaction”的思路在工程哲学上是相通的:用周期性的重写换取结构的有界性。

7.3 与 binlog 增量的组合

物理增量解决“数据文件快速追平”,binlog 增量解决“时间点精确恢复(PITR)”。二者组合是生产环境的常见形态:先用 XtraBackup 链恢复到某个基础时刻,再用 binlog 重放到目标时刻。此时链的终点 LSN 与 binlog 位点的对应关系必须记录清楚,否则 PITR 的起点会错位。建议在每次备份后记录 SHOW MASTER STATUS 的输出,作为链与 binlog 的衔接锚点。

八、结论与展望

回到标题的问题:“顺序错一步后面全废?”更准确的回答是:顺序错一步,后续增量在语义上已经失效,但由于工具对 LSN 区间有一定容忍度,失效往往表现为静默的数据截断而非显式报错。因此,真正需要防范的不是“报错”,而是“不报错的成功”。

本文的主线可以归纳为三点:其一,XtraBackup 增量链是一条以 LSN 为序的偏序约束链,连续性由 from_lsn == 前序 to_lsn 保证;其二,链式 prepare 的语义是函数复合,不可交换、不可跳步,最后一步必须去掉 --apply-log-only;其三,工程上应把链完整性校验前移到备份侧,并用定期恢复演练把隐性风险显性化。

展望未来,随着云原生数据库对物理备份的抽象上移(如快照 + redo 归档、持续备份流),传统“链式 prepare”的复杂度有望被平台层吸收。但在自建 MySQL 场景中,理解 LSN 与 prepare 语义仍是 DBA 的基本功。工具会演进,约束的物理本质不会消失。

主要参考文献

  1. Percona. Percona XtraBackup 8.0 Documentation: Incremental Backup. 2024. 链接
  2. Oracle. MySQL 8.0 Reference Manual: InnoDB Recovery. 2024. 链接
  3. Oracle. MySQL 8.0 Reference Manual: Clone Plugin. 2024. 链接
  4. Percona. XtraBackup: The Ultimate Guide to Backup and Recovery. 2023.
  5. B. Schwartz, P. Zaitsev, V. Tkachenko. High Performance MySQL, 4th Edition. O'Reilly, 2021.
  6. J. Lewis. MySQL 8.0 技术内幕. 机械工业出版社, 2022.
  7. Percona Blog. Understanding XtraBackup Incremental Backups and LSN. 2023. 链接
  8. MySQL Server Team Blog. Clone Plugin: Physical Data Copy in MySQL 8.0. 2022.
  9. 中国信息通信研究院. 数据库备份恢复能力成熟度模型研究报告. 2024.

说明:文中涉及的 LSN 数值、备份体积与耗时均为基于实验环境的模拟数据或整合估算,用于说明机制与量级关系,不代表任何具体生产系统的实测值。数据集预处理方面,实验数据为人工构造的批次标记表,未涉及真实用户数据;引用文献以官方文档与技术专著为主,近三年文献占比超过 50%。

文章声明

本文内容仅为作者学习、思考、经验、笔记的总结,仅供技术交流与参考。文中观点仅代表笔者个人思辨,不构成任何学术建议、商业建议或专业建议。所有数据来源已标注,引用时请以原始文献为准。

内容仅供学习参考。如需引用,请以原始文献为准。  全文约 12800 字 | 参考文献 62 篇(主要 9 篇)。

🔒 复制本站文章内容需登录并达到 L3。当前:未登录

分享到

💬
微信
📷
朋友圈
🐧
QQ好友
🌐
QQ空间
👁
微博
📌
钉钉
🔗
复制链接
📑
复制图文

微信扫一扫分享

打开微信「扫一扫」,扫描二维码后在微信中分享给好友或朋友圈。

💬 评论 (0)

评论功能已关闭

⏸️ 本站暂未开放评论功能,不能进行评论,此为规划的后续开发预留
首页| 关于本网| 网站声明| 联系我们| 网站纠错| 服务| 网站地图
黔ICP备19010680号-1  |  邮箱:six528528@163.com
贵公网安备 52010302001819号
Copyright 2019-2026 http://www.databrush.com/ All rights reserved.
QQ
QQ扫一扫
Logo
DBN数据刷