MySQL 数据备份与恢复策略(xtrabackup 实战)
你遇到过备份恢复比预计慢 10 倍的情况吗?
我在某电商公司遇到过这么一出:DBA 小哥凌晨 3 点跑 mysqldump 备份一个 500GB 的订单库,跑了 6 个小时还没完,结果备份窗口直接撞上了早高峰。更离谱的是,第二天需要恢复一张被误删的表,发现 mysqldump 的备份文件里只有「表的定义」,没有数据——因为 --single-transaction 参数没加,分表备份时根本不保证一致性。这就是典型的备份策略设计失误。
MySQL 的备份方案不是「选一个工具跑就完事」,而是要根据数据量大小、恢复时间目标(RTO) 和恢复点目标(RPO) 来设计分层策略。本文从最常用的 xtrabackup 入手,讲清楚热备原理、增量备份策略和恢复实战中的坑。
备份方案选型:mysqldump 还是 xtrabackup?
先看一个简单的对比表:
| 特性 | mysqldump | xtrabackup |
|---|---|---|
| 备份类型 | 逻辑备份(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. 全量备份
# 全量备份,输出到指定目录
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 之前)、ibdata1、undo 表空间等文件。
2. Apply Log(准备备份)
# 应用 Redo Log,使备份处于一致状态
xtrabackup --prepare --target-dir=/backup/mysql/full-20260720这一步不能省略。如果没有 --prepare,直接 --copy-back 恢复出来的数据是不一致的,启动 MySQL 后 InnoDB 会认为数据文件损坏。
3. 恢复数据
# 停掉 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 mysql4. 增量备份
增量备份的核心逻辑是基于 LSN(Log Sequence Number):记录上次备份时的 LSN,下次备份只拷贝 LSN 之后发生变更的数据页。
# 先做全量备份(周一凌晨)
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-202607225. 增量备份的恢复
增量恢复比全量恢复多一步:需要先将全量备份和增量备份合并。
# 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 越小,恢复方案越昂贵。
常见分层策略:
| 业务等级 | RPO | RTO | 备份方案 |
|---|---|---|---|
| 核心(支付、订单) | < 5 分钟 | < 30 分钟 | 每周全量 + 每日增量 + Binlog 实时备份 |
| 重要(用户、商品) | < 1 小时 | < 2 小时 | 每周全量 + 每日增量 |
| 一般(日志、统计) | < 1 天 | < 4 小时 | 每周全量 |
Binlog 实时备份实现:
# 方案一: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 文件损坏。
解法:备份完成后立即做完整性校验:
# 备份后立即做 --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 连续性:
# 获取上次备份的 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 不行)。跨版本迁移请用 mysqldump 或 mysqlpump。
坑 4:备份窗口被写爆
现象:每天凌晨 2 点跑全量备份,但业务峰值在凌晨 1-3 点(海外业务),备份 IO 导致业务响应变慢。
解法:用 --throttle 和 --parallel 控制备份资源使用:
# 白天限速,凌晨放开
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):
# 备份后自动压缩上传
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:
- 选型看数据量:50GB 以下用
mysqldump,以上用xtrabackup,两者互补。 - 备份不等同于恢复:80% 的备份恢复失败案例是因为备份文件损坏或不可用,定期做恢复演练是唯一解法。
- 增量备份 ≠ 全量备份的简化版:LSN 连续性校验、
--apply-log-only与最终 prepare 的区别、增量链的可靠性——这些细节不注意,恢复时就是灾难。 - RTO/RPO 是业务需求,不是技术指标:问清楚业务方「能接受丢多少数据」和「能接受停多久」,再设计备份策略。
- 异地容灾是底线:备份文件跟源数据在同一台机器上,等于没有备份。
最后给个建议:每个月找一台测试服务器,把生产环境最新的备份完整恢复一次,然后跑一遍 pt-table-checksum 校验数据一致性。这个动作虽然花时间,但能发现 90% 的备份隐患。
参考:
- Percona XtraBackup 官方文档:https://docs.percona.com/percona-xtrabackup/
- MySQL 备份与恢复最佳实践:https://dev.mysql.com/doc/refman/8.0/en/backup-and-recovery.html
- 《高性能 MySQL》Baron Schwartz 等