Skip to content

MySQL 数据备份与恢复策略(xtrabackup 实战)

你遇到过备份恢复比预计慢 10 倍的情况吗?

我在某电商公司遇到过这么一出:DBA 小哥凌晨 3 点跑 mysqldump 备份一个 500GB 的订单库,跑了 6 个小时还没完,结果备份窗口直接撞上了早高峰。更离谱的是,第二天需要恢复一张被误删的表,发现 mysqldump 的备份文件里只有「表的定义」,没有数据——因为 --single-transaction 参数没加,分表备份时根本不保证一致性。这就是典型的备份策略设计失误

MySQL 的备份方案不是「选一个工具跑就完事」,而是要根据数据量大小恢复时间目标(RTO)恢复点目标(RPO) 来设计分层策略。本文从最常用的 xtrabackup 入手,讲清楚热备原理、增量备份策略和恢复实战中的坑。

备份方案选型:mysqldump 还是 xtrabackup?

先看一个简单的对比表:

特性mysqldumpxtrabackup
备份类型逻辑备份(SQL 语句)物理备份(数据文件拷贝)
备份速度慢(逐行导出)快(物理拷贝)
恢复速度极慢(SQL 重放)快(直接复制数据文件)
跨版本兼容✅ 灵活❌ 需版本匹配
部分恢复✅ 方便❌ 需先全量恢复
增量备份❌ 不支持✅ 支持(基于 LSN)
50GB 以上大库❌ 不推荐✅ 推荐

选型准则

  • 小库(< 50GB)、需要跨版本迁移、只要部分表 → mysqldump
  • 大库(> 50GB)、需要增量备份、RTO 要求高 → xtrabackup
  • 生产环境的完整备份策略,通常两者结合:xtrabackup 做全量+增量 + mysqldump 做单表快速恢复

xtrabackup 热备原理:为什么它不锁表?

xtrabackup 的核心思路是物理拷贝 + Redo Log 应用,分三步走:

1. 启动拷贝 → 后台线程直接复制 InnoDB 数据文件(.ibd)
2. 记录 LSN → 同时记录 Redo Log 中当前写入位置(LSN)
3. Apply Log → 备份完成后,将未写入数据文件的 Redo Log 应用到备份副本

关键点在于:xtrabackup 不等待 MySQL 把脏页刷盘。它直接拷贝数据文件,这些文件在拷贝瞬间可能包含部分已提交但未落盘的事务,以及部分未提交的事务。所以 --apply-log 这一步至关重要——它把 Redo Log 中已提交但未写入数据文件的变更前滚,同时把未提交的事务回滚,使备份处于一致状态。

这跟 mysqldump --single-transaction 的原理不同:mysqldump 利用 MVCC 的快照读,读取的是事务开始时的数据版本;而 xtrabackup 是物理级别的拷贝,通过 Redo Log 保证一致性。

实战:完整备份恢复流程

1. 全量备份

bash
# 全量备份,输出到指定目录
xtrabackup --backup \
  --target-dir=/backup/mysql/full-20260720 \
  --user=root \
  --password=your_password \
  --parallel=4 \
  --throttle=100

# 参数说明:
# --parallel=4    → 4 个线程并行拷贝,提高速度
# --throttle=100  → 限制每秒 IO 操作数,避免影响线上业务

备份完成后,/backup/mysql/full-20260720/ 目录就是一份完整的 MySQL 数据目录,包含所有 .ibd.frm(MySQL 8.0 之前)、ibdata1undo 表空间等文件。

2. Apply Log(准备备份)

bash
# 应用 Redo Log,使备份处于一致状态
xtrabackup --prepare --target-dir=/backup/mysql/full-20260720

这一步不能省略。如果没有 --prepare,直接 --copy-back 恢复出来的数据是不一致的,启动 MySQL 后 InnoDB 会认为数据文件损坏。

3. 恢复数据

bash
# 停掉 MySQL
systemctl stop mysql

# 备份原数据目录(保留现场)
mv /var/lib/mysql /var/lib/mysql.bak

# 将备份文件恢复到数据目录
xtrabackup --copy-back --target-dir=/backup/mysql/full-20260720

# 恢复目录权限
chown -R mysql:mysql /var/lib/mysql

# 启动 MySQL
systemctl start mysql

4. 增量备份

增量备份的核心逻辑是基于 LSN(Log Sequence Number):记录上次备份时的 LSN,下次备份只拷贝 LSN 之后发生变更的数据页。

bash
# 先做全量备份(周一凌晨)
xtrabackup --backup --target-dir=/backup/mysql/full-20260720

# 周二增量备份
xtrabackup --backup \
  --incremental-basedir=/backup/mysql/full-20260720 \
  --target-dir=/backup/mysql/inc-20260721

# 周三增量备份(基于周二的增量)
xtrabackup --backup \
  --incremental-basedir=/backup/mysql/inc-20260721 \
  --target-dir=/backup/mysql/inc-20260722

5. 增量备份的恢复

增量恢复比全量恢复多一步:需要先将全量备份和增量备份合并

bash
# 1. Prepare 全量备份
xtrabackup --prepare --apply-log-only --target-dir=/backup/mysql/full-20260720

# 2. 将增量合并到全量
xtrabackup --prepare \
  --apply-log-only \
  --incremental-dir=/backup/mysql/inc-20260721 \
  --target-dir=/backup/mysql/full-20260720

# 3. 合并第二个增量
xtrabackup --prepare \
  --apply-log-only \
  --incremental-dir=/backup/mysql/inc-20260722 \
  --target-dir=/backup/mysql/full-20260720

# 4. 最后一次 prepare(不加 --apply-log-only,会做 rollback)
xtrabackup --prepare --target-dir=/backup/mysql/full-20260720

# 5. 恢复(同全量步骤)
xtrabackup --copy-back --target-dir=/backup/mysql/full-20260720

注意:--apply-log-only 表示只做 Redo Log 前滚(redo),不做回滚(undo)。因为增量备份可能还有后续的增量需要合并,如果在中间步骤就做了 rollback,后续增量就合并不进去了。最后一次 prepare 不加 --apply-log-only,同时做 redo 和 undo

备份策略设计:RTO 和 RPO 怎么定?

纯讲技术是 P6 水平,P7+ 要能根据业务需求设计备份策略。

核心公式

  • RPO(恢复点目标) = 你能接受丢多少数据。RPO 越小,备份频率越高,成本越高。
  • RTO(恢复时间目标) = 你能接受停多久。RTO 越小,恢复方案越昂贵。

常见分层策略:

业务等级RPORTO备份方案
核心(支付、订单)< 5 分钟< 30 分钟每周全量 + 每日增量 + Binlog 实时备份
重要(用户、商品)< 1 小时< 2 小时每周全量 + 每日增量
一般(日志、统计)< 1 天< 4 小时每周全量

Binlog 实时备份实现

bash
# 方案一:mysqlbinlog 实时同步
mysqlbinlog \
  --raw \
  --read-from-remote-server \
  --stop-never \
  --host=master-host \
  --port=3306 \
  --user=repl \
  --password=repl_password \
  --result-file=/backup/binlog/

# 方案二:用 binlog server 工具(如 binlogctl)
# 这种方案在 MySQL 崩了之后,可以用全量+增量+binlog 恢复到故障前的最后一秒

恢复时间估算(以 500GB 库为例):

步骤耗时说明
全量备份~2 小时xtrabackup + 4 线程
Prepare 全量~40 分钟应用 Redo Log
合并增量(7 天)~30 分钟每天约 5 分钟
Apply Binlog(1 小时量)~10 分钟重放 binlog
Copy-back~30 分钟文件拷贝到数据目录
总计~3.5 小时满足 RTO < 4 小时

生产中的 5 个大坑

坑 1:备份文件损坏了但不自知

现象:备份跑完没报错,但恢复时发现 .ibd 文件损坏。

解法:备份完成后立即做完整性校验:

bash
# 备份后立即做 --prepare + 校验
xtrabackup --prepare --target-dir=/backup/mysql/full-20260720
xtrabackup --stats --target-dir=/backup/mysql/full-20260720

# 计算文件 MD5,用于恢复时校验
cd /backup/mysql/full-20260720 && find . -type f -exec md5sum {} \; > /backup/mysql/checksums/full-20260720.md5

坑 2:增量备份基于错误的 basedir

现象:增量备份的 --incremental-basedir 指向了错误的路径,导致备份了全量数据(相当于又做了一次全量),磁盘空间瞬间爆炸。

解法:在备份脚本中显式校验 LSN 连续性:

bash
# 获取上次备份的 LSN
LAST_LSN=$(cat /backup/mysql/inc-20260721/xtrabackup_checkpoints | grep to_lsn | awk '{print $3}')

# 本次增量备份检查 basedir 的 LSN
CURRENT_LSN=$(xtrabackup --backup --incremental-basedir=/backup/mysql/inc-20260721 --print-param 2>&1 | grep "last_lsn" | awk '{print $3}')

# 如果差距过大(> 1TB 的 LSN 差距)则报警
if [ $((CURRENT_LSN - LAST_LSN)) -gt 1000000000000 ]; then
  echo "WARNING: LSN gap too large, check basedir path!"
  exit 1
fi

坑 3:恢复时 MySQL 版本不一致

现象:xtrabackup 备份的 MySQL 8.0.28,恢复目标机器是 MySQL 8.0.32,启动后报 data dictionary 版本错误。

解法xtrabackup 的版本必须与 MySQL 大版本匹配。具体来说,xtrabackup 8.0.x 可以备份/恢复 MySQL 8.0.x 的任何小版本,但不能跨大版本(8.0 → 8.1 不行)。跨版本迁移请用 mysqldumpmysqlpump

坑 4:备份窗口被写爆

现象:每天凌晨 2 点跑全量备份,但业务峰值在凌晨 1-3 点(海外业务),备份 IO 导致业务响应变慢。

解法:用 --throttle--parallel 控制备份资源使用:

bash
# 白天限速,凌晨放开
if [ $(date +%H) -ge 9 ] && [ $(date +%H) -lt 23 ]; then
  THROTTLE=50
  PARALLEL=2
else
  THROTTLE=500
  PARALLEL=8
fi

xtrabackup --backup \
  --throttle=$THROTTLE \
  --parallel=$PARALLEL \
  ...

坑 5:备份文件没有异地容灾

现象:服务器硬盘故障,备份文件也在同一台机器上,全量 + 增量一起没了。

解法:备份完成后自动上传到对象存储(OSS/S3):

bash
# 备份后自动压缩上传
tar -czf /backup/mysql/full-20260720.tar.gz /backup/mysql/full-20260720
aws s3 cp /backup/mysql/full-20260720.tar.gz s3://my-db-backup/mysql/

# 清理本地 7 天前的备份
find /backup/mysql/ -name "*.tar.gz" -mtime +7 -delete

总结

MySQL 备份不是「跑个 mysqldump 就完事」的简单活。几个关键 takeaways:

  1. 选型看数据量:50GB 以下用 mysqldump,以上用 xtrabackup,两者互补。
  2. 备份不等同于恢复:80% 的备份恢复失败案例是因为备份文件损坏或不可用,定期做恢复演练是唯一解法。
  3. 增量备份 ≠ 全量备份的简化版:LSN 连续性校验、--apply-log-only 与最终 prepare 的区别、增量链的可靠性——这些细节不注意,恢复时就是灾难。
  4. RTO/RPO 是业务需求,不是技术指标:问清楚业务方「能接受丢多少数据」和「能接受停多久」,再设计备份策略。
  5. 异地容灾是底线:备份文件跟源数据在同一台机器上,等于没有备份。

最后给个建议:每个月找一台测试服务器,把生产环境最新的备份完整恢复一次,然后跑一遍 pt-table-checksum 校验数据一致性。这个动作虽然花时间,但能发现 90% 的备份隐患。

参考

手撕 → 框架 → 生产化,一步步把 AI Agent 工程化搞透。