3、物理备份实战:XtraBackup全量备份原理、增量备份与差异备份的区别、备份集验证

物理备份,说白了就是直接拷贝数据库的数据文件。逻辑备份用 mysqldump 导出 SQL,物理备份则直接操作磁盘上的 .ibd、.frm 这些文件。你想想看,几十 GB 的数据库,用 mysqldump 导出再导入,那得等到什么时候?物理备份的速度优势就在这里体现出来了。

XtraBackup 是目前 MySQL 物理备份的事实标准。我最早接触它是在 Percona 5.6 时代,那时候还叫 innobackupex,现在已经统一成 xtrabackup 命令了。嗯,这里要注意,从 8.0 版本开始,Percona 已经废弃了 innobackupex,全部用 xtrabackup 替代。

核心认知:XtraBackup 备份的是物理文件,不是逻辑数据。它直接拷贝 InnoDB 的数据文件、redo log、undo log,以及表结构定义文件。恢复时也是直接把这些文件放回数据目录,不需要逐条执行 SQL。

3.1 全量备份原理

全量备份,就是把整个数据库实例的所有数据文件完整拷贝一份。XtraBackup 是怎么做到在线热备而不影响业务的?

它的核心机制是 redo log 的持续跟踪。备份开始时,XtraBackup 会启动一个后台线程,专门监控 redo log 的变化。然后它开始拷贝数据文件,这个过程可能持续几分钟甚至几小时。在拷贝期间,数据库还在不断写入,数据文件也在变化。

这就产生了一个问题:拷贝出来的数据文件,在时间点上是不一致的。比如表 A 的数据拷贝完了,但表 B 还没拷贝完,这时候数据库崩溃了,恢复时就会出问题。

XtraBackup 的解决方案是:拷贝完所有数据文件后,利用之前跟踪的 redo log,对数据文件执行一次 崩溃恢复(apply redo log)。这样就能把数据文件推到同一个一致的时间点。

我个人习惯把全量备份的命令写成脚本,加上时间戳命名,方便后续管理:

# 全量备份命令示例
xtrabackup --backup \
  --target-dir=/backup/full/$(date +%Y%m%d_%H%M%S) \
  --datadir=/var/lib/mysql \
  --user=backup_user \
  --password=backup_pass \
  --host=127.0.0.1 \
  --port=3306

备份完成后,还需要执行一步 prepare 操作,让备份集处于可用状态:

# 对全量备份执行 prepare
xtrabackup --prepare \
  --target-dir=/backup/full/20250315_020000

为什么要 prepare?因为备份出来的数据文件,redo log 可能还没完全应用,处于"不一致"状态。prepare 就是模拟 MySQL 的崩溃恢复过程,把数据文件推到一致点。我在项目中遇到过有人忘记执行这一步,直接拿备份文件去恢复,结果 MySQL 启动时报错,数据文件损坏。嗯,这个坑踩过一次就再也不会忘了。

3.2 增量备份与差异备份的区别

全量备份虽然完整,但每天做一次全量,磁盘空间和备份时间都扛不住。这时候就需要增量备份或差异备份来帮忙。

这两个概念经常被混淆,我简单解释一下:

  • 增量备份:只备份自上一次备份(无论全量还是增量)以来发生变化的数据。
  • 差异备份:只备份自上一次全量备份以来发生变化的数据。

举个例子你就明白了。假设周一做了全量备份,周二、周三、周四分别做备份:

备份策略 周二备份内容 周三备份内容 周四备份内容
增量备份 周一~周二变化 周二~周三变化 周三~周四变化
差异备份 周一~周二变化 周一~周三变化 周一~周四变化

看到了吗?差异备份每次都是跟全量比,所以越往后备份的数据量越大。增量备份只跟上一次比,所以每次数据量相对较小。

恢复时的区别更明显:

  • 增量恢复:需要全量 + 所有增量备份链,按顺序 apply。比如周四出问题,需要恢复顺序是:全量 → 增量1 → 增量2 → 增量3。
  • 差异恢复:只需要全量 + 最后一次差异备份。比如周四出问题,只需要:全量 → 差异3。

我曾经在一个日活千万的电商平台上做过对比测试。每天数据变更量约 5GB,全量备份 200GB。增量备份每天 5GB,差异备份从周一的 5GB 涨到周日的 35GB。恢复时间上,增量恢复需要 2 小时(因为要按顺序 apply 7 个增量),差异恢复只需要 40 分钟。

所以我的建议是:

  • 如果数据变更量小,且恢复时间要求不高,用增量备份可以节省磁盘空间。
  • 如果数据变更量大,或者对恢复速度有严格要求,用差异备份更合适。
  • 我个人习惯是:周日做全量,周一到周六做差异备份。这样恢复时最多只需要全量 + 一个差异,速度快,管理也简单。

XtraBackup 的增量备份命令如下:

# 基于全量备份做增量备份
xtrabackup --backup \
  --target-dir=/backup/inc1/ \
  --incremental-basedir=/backup/full/20250315_020000 \
  --datadir=/var/lib/mysql \
  --user=backup_user \
  --password=backup_pass

# 基于上一次增量做下一次增量
xtrabackup --backup \
  --target-dir=/backup/inc2/ \
  --incremental-basedir=/backup/inc1/ \
  --datadir=/var/lib/mysql \
  --user=backup_user \
  --password=backup_pass

增量备份的 prepare 需要特殊处理,不能直接 --prepare:

# 先 prepare 全量备份(带 --apply-log-only 防止回滚未提交事务)
xtrabackup --prepare --apply-log-only \
  --target-dir=/backup/full/20250315_020000

# 依次 apply 每个增量到全量上
xtrabackup --prepare --apply-log-only \
  --target-dir=/backup/full/20250315_020000 \
  --incremental-dir=/backup/inc1/

xtrabackup --prepare --apply-log-only \
  --target-dir=/backup/full/20250315_020000 \
  --incremental-dir=/backup/inc2/

# 最后一个增量不需要 --apply-log-only,让 XtraBackup 执行完整的崩溃恢复
xtrabackup --prepare \
  --target-dir=/backup/full/20250315_020000 \
  --incremental-dir=/backup/inc3/

注意:增量备份链不能断。如果中间某个增量备份损坏或丢失,后续所有增量都无法应用。我曾经在客户现场遇到过这种情况——磁盘故障导致一个增量备份文件损坏,结果只能恢复到上一个全量备份的时间点,丢失了将近 12 小时的数据。所以增量备份的完整性验证非常重要。

3.3 备份集验证

备份做完了,不代表就万事大吉了。你想想看,如果备份文件本身是坏的,等到需要恢复时才发现,那可就真叫天天不应了。

备份集验证,说白了就是确认备份文件是可用的、完整的。我把它分为三个层次:

3.3.1 基本完整性检查

XtraBackup 在备份完成后,会在备份目录下生成一些元数据文件,比如 xtrabackup_checkpoints、xtrabackup_info。我们可以通过这些文件快速判断备份是否完整。

# 查看备份的 checkpoint 信息
cat /backup/full/20250315_020000/xtrabackup_checkpoints

# 输出示例
backup_type = full-prepared
from_lsn = 0
to_lsn = 1234567890
last_lsn = 1234567895
compact = 0
recover_binlog_info = 0

关键字段说明:

  • backup_type:如果是 full-prepared,说明已经执行过 prepare,可以直接用于恢复。
  • to_lsn 和 last_lsn:这两个值应该非常接近,如果差距过大,说明备份过程中可能有问题。

3.3.2 模拟恢复验证

这是最可靠的验证方式。把备份集恢复到一台测试实例上,然后启动 MySQL,检查数据是否完整。

# 1. 准备一个空的恢复目录
mkdir -p /tmp/restore_test

# 2. 执行 restore(拷贝文件到恢复目录)
xtrabackup --copy-back \
  --target-dir=/backup/full/20250315_020000 \
  --datadir=/tmp/restore_test

# 3. 修改权限
chown -R mysql:mysql /tmp/restore_test

# 4. 启动 MySQL 测试实例
mysqld --datadir=/tmp/restore_test --port=3307 &
# 或者用 mysqld_safe

# 5. 连接测试实例,检查数据
mysql -h127.0.0.1 -P3307 -e "SELECT COUNT(*) FROM mydb.mytable;"

# 6. 验证完成后关闭测试实例
mysqladmin -h127.0.0.1 -P3307 shutdown

我在项目中遇到过这样的情况:备份文件看起来完整,checkpoints 文件也正常,但模拟恢复时发现某个表空间文件损坏,MySQL 启动报错。幸亏提前发现了,否则真到灾难恢复时,后果不堪设想。

我的经验:每周至少做一次完整的模拟恢复验证。不要只在备份脚本里加个 --check 参数就完事了。真正的恢复流程涉及文件权限、路径配置、参数设置等多个环节,只有完整跑一遍才能发现问题。

3.3.3 自动化验证脚本

手动验证太麻烦,我一般会写一个自动化脚本,每天凌晨备份完成后自动执行验证:

#!/bin/bash
# 备份验证脚本示例

BACKUP_DIR="/backup/full/$(date +%Y%m%d)"
TEST_DIR="/tmp/restore_verify"
MYSQL_DATA="/var/lib/mysql_test"

# 清理上次验证环境
rm -rf $TEST_DIR $MYSQL_DATA
mkdir -p $TEST_DIR $MYSQL_DATA

# 执行 prepare(如果还没 prepare)
xtrabackup --prepare --target-dir=$BACKUP_DIR

# 执行 restore
xtrabackup --copy-back --target-dir=$BACKUP_DIR --datadir=$MYSQL_DATA
chown -R mysql:mysql $MYSQL_DATA

# 启动测试实例
mysqld --datadir=$MYSQL_DATA --port=3307 \
  --socket=/tmp/mysql_verify.sock \
  --skip-networking &
sleep 5

# 检查关键表
mysql -S /tmp/mysql_verify.sock -e "
  SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_ROWS
  FROM information_schema.TABLES
  WHERE TABLE_SCHEMA NOT IN ('mysql','sys','performance_schema','information_schema')
  ORDER BY TABLE_ROWS DESC LIMIT 10;
"

# 检查是否有损坏的表
mysql -S /tmp/mysql_verify.sock -e "CHECK TABLE mydb.mytable;"

# 关闭测试实例
mysqladmin -S /tmp/mysql_verify.sock shutdown

# 清理
rm -rf $TEST_DIR $MYSQL_DATA

echo "备份验证完成: $(date)"

这个脚本可以放在 crontab 里每天执行。如果验证失败,就发送告警通知 DBA 处理。

3.4 知识体系总览

下面这张图总结了 XtraBackup 物理备份的核心逻辑:

XtraBackup 物理备份知识体系 全量备份 拷贝所有数据文件 跟踪 redo log 变化 prepare 执行崩溃恢复 增量备份 基于上一次备份的变化 数据量小,节省空间 恢复需按顺序 apply 差异备份 基于上一次全量的变化 越往后数据量越大 恢复只需全量+最后一个 备份集验证 完整性检查 → 模拟恢复验证 → 自动化验证脚本 恢复策略选择 数据变更量小 → 增量备份 | 恢复速度要求高 → 差异备份 核心原则:备份是手段,恢复是目的 定期验证备份集可用性,确保灾难发生时能快速恢复

一句话总结:全量备份是基础,增量备份省空间,差异备份省时间,备份集验证是底线。三者结合使用,才能构建可靠的数据库备份体系。