早些年我一直用 mysqldump 做例行备份,数据量小的时候没觉得有什么,等库涨到一两百 GB,dump 一次加上恢复演练的时间越来越捉襟见肘。后来接手的一套核心业务系统,单实例近 3TB,半夜跑备份动不动把业务拖垮,我才下定决心把备份方案整体换成物理备份。现在线上跑的主力就是 Percona 的 xtrabackup,全量加增量配合使用,RPO 控制在 15 分钟以内,恢复演练也稳定得多。这套东西真正用顺了之后,你会发现它没有想象中复杂,但坑也确实不少,这篇把全量、增量备份和恢复的实操路径完整梳理一遍,适合已经熟悉 MySQL 基本运维、想在备份层面升级的中级同学参考。
1. 为什么要把目光从逻辑备份转向 xtrabackup
1.1 备份方案对比
逻辑备份和物理备份是两条完全不同的路线。mysqldump 是逻辑导出,它把表结构、数据行、触发器、事件翻译成 SQL 语句,然后一条条执行导入。这种方式胜在跨版本、跨平台迁移方便,备份文件也能直接编辑,很适合小库和开发环境。但恢复的时候要走解析 SQL、重建索引、重放 binlog 这条链路,大库恢复起来非常痛苦。
xtrabackup 走的是物理备份路线,它直接复制 MySQL 数据目录下的文件,包括 ibdata1、ib_logfile 或 redo log、每个表的 .ibd 文件。它通过复制 InnoDB 数据文件加对应 redo log 的方式,在备份期间不阻塞业务读写,拿到的是一个内部一致的数据集,恢复时把文件拷回数据目录、再执行崩溃恢复逻辑就行。同样几百 GB 的量,mysqldump 可能要跑几小时,xtrabackup 往往几十分钟搞定,差距非常明显。
| 对比项 | mysqldump 逻辑备份 | xtrabackup 物理备份 |
|---|---|---|
| 备份方式 | SQL 语句导出 | 数据文件加 redo log 复制 |
| 对在线业务影响 | 大表有锁表或长时间读压力 | 几乎无阻塞,适合生产 |
| 恢复速度 | 慢,需解析 SQL | 快,文件恢复加崩溃恢复 |
| 增量能力 | 需要靠 binlog 配合 | 原生支持基于 LSN 的增量 |
| 适用场景 | 小库迁移、开发环境 | 大库日常备份、快速恢复 |
| 备份文件可读性 | 可直接查看内容 | 不可直接读取,需 xtrabackup 解析 |
这里有个容易误解的点:xtrabackup 并不只适合大库。中小库想缩短恢复时间、想把增量备份做得更简单,同样值得切。我接触过的项目里,很多库也就二三十 GB,用了 xtrabackup 之后,恢复演练时间从“一晚上”缩短到“一顿饭”。
1.2 全量与增量的场景边界
全量备份就是把整个数据目录做一份拷贝,它是所有恢复方案的底稿。增量备份则基于全量备份基础上,只记录从上一次备份以来变更过的数据页,核心依赖是 InnoDB 的 LSN。
这两个概念要说完整,得先理清 InnoDB 持久化机制。InnoDB 在内存里维护脏页,后台线程会定期把脏页刷到磁盘,同时写入 redo log 保证崩溃安全。xtrabackup 备份的时候记住当前 checkpoint 的 LSN,并将其与数据文件一并记录。增量备份时,它会扫描每个表空间的数据页,找出 LSN 比基准备份更新过的页,只复制这些增量数据页。
所以生产环境的备份策略通常是:每周一次全量,每天一次增量,甚至可以细化到每几小时一次增量。全量解决“我要一个完整基线”,增量解决“我要把备份窗口压缩到最短”。两者配合,既能控制存储成本,又能在出故障时快速追到接近故障发生的时间点。
2. 动手之前:安装、兼容性与备份链路设计
2.1 版本与兼容性选择
xtrabackup 的版本与 MySQL 版本强相关,选错版本是整个备份链路最大的隐患。我用过一个教训深刻的案例:给 MySQL 8.0 数据库装了 2.4 版本的 xtrabackup,备份阶段直接报错,查文档才发现 2.4 只支持 MySQL 5.7 及更早版本。加粗显示这条规则:
注意:MySQL 8.0 / 8.4 请使用 xtrabackup 8.0 系列,MySQL 5.7 请使用 xtrabackup 2.4 系列,Percona Server 也遵循同样规则。
版本之外,还要确认 glibc 环境和操作系统架构。xtrabackup 官方提供的是编译好的二进制包,但网上不少教程让你从源码编译,说实话生产环境没必要。直接下载对应发行版的二进制包,放到 /usr/local/xtrabackup 下,设置好 PATH 即可。我在某套 CentOS 7 环境上就一直用 2.4 版本对应的 rpm 包,省心很多。
安装完成之后先跑一下版本确认:
xtrabackup --version如果输出中带着 MySQL 版本兼容范围,说明安装正常。这里还会涉及一个依赖库问题,最常见的是缺少 libev、libaio 等,报错信息通常会直接告诉你是哪个 .so 文件找不到,补齐即可。
2.2 备份目录、存储与权限规划
备份目录规划是很多人忽视的点。磁盘空间不足导致备份失败,是生产环境出现频率极高的问题。全量备份的体积一般比原数据目录小一点,但增量备份会随着时间增长,加上 redo log 拷贝和临时文件,实际占用往往比理论值要高。我习惯给备份目录留出源数据目录 1.5 到 2 倍的空间,至少不能低于源库大小。
权限上,xtrabackup 连接 MySQL 需要两类权限:一类是操作系统层面的文件读写权限,尤其是对数据目录的读权限和备份目录的写权限;另一类是 MySQL 层面的 BACKUP_ADMIN、RELOAD、PROCESS、LOCK TABLES、REPLICATION CLIENT 等权限。生产环境不建议直接用 root 跑备份,最好单独建一个备份账号,按最小权限原则授权。示例:
CREATE USER 'backup_user'@'localhost' IDENTIFIED BY 'StrongPass123'; GRANT BACKUP_ADMIN, PROCESS, RELOAD, LOCK TABLES, REPLICATION CLIENT, SELECT ON *.* TO 'backup_user'@'localhost';注意,MySQL 8.0 引入 BACKUP_ADMIN 权限,老版本没有这个权限也不影响。备份账号建好后,先拿它跑一次空备份验证权限是否完整,免得半夜备份任务因权限失败。
2.3 理解 LSN,才能理解增量备份
增量备份绕不开 LSN(Log Sequence Number),它本质上是 InnoDB 写入日志的顺序编号,每次数据页的修改都会让 LSN 增长。你可以把它理解为 InnoDB 的“版本指针”,每个数据页上都记录着最近一次变更时的 LSN。
xtrabackup 全量备份完成后,会在备份目录生成 xtrabackup_checkpoints 文件,里面记着该备份对应的 to_lsn。增量备份运行时,会以这个 to_lsn 作为基线,只复制那些页 LSN 大于基线的数据页。恢复时,把增量内容合并到全量备份上,再统一做崩溃恢复。
这个机制决定了一件重要的事:增量备份不是对日志文件的追加,而是对数据页的增量同步。所以增量备份之间是不能跳级的,比如你不能跳过一个增量,拿第二个增量去合并第一个全量,那样数据会不完整。后面实操时我会详细展示怎么查看 checkpoints 信息。
3. 全量备份完整实操
3.1 执行一次“能跑”的全量备份
全量备份的命令其实很简洁,但参数背后的含义值得逐项理解。我刚上手时只记参数不记原理,结果换到不同版本或者换了一台机器就慌,还是要把参数吃透。
标准命令:
xtrabackup --backup \ --target-dir=/backup/mysql/full/$(date +%F_%H-%M) \ --user=backup_user \ --password='StrongPass123' \ --host=127.0.0.1 \ --port=3306 \ --no-server-version-check我解释几个关键参数:
- --backup 进入备份模式,xtrabackup 会先连接 MySQL 获取当前状态,然后开始拷贝数据文件。
- --target-dir 指定备份输出目录,这个目录必须不存在或者为空,否则 xtrabackup 会直接报错。
- --host 和 --port 在通过 TCP 连接时使用,如果本机 socket 连接,要指定 --socket,我习惯写清楚 TCP 连接,便于排查网络层面的问题。
- --no-server-version-check 是跳过版本校验。不是所有场景都建议加,但如果你确认版本兼容且只是做验证,加上能避免不必要的版本检查报错。
备份过程中日志会打印大量信息,重点看两行:
- 开始时的 “Starting backup”
- 结束时的 “completed OK”
只要最后是 completed OK,备份就成功。如果中间报错,基本可以从日志最后十几行找到根因。备份完成之后,看一眼目录内容:
ls -lh /backup/mysql/full/2025-06-01_02-00/你会看到 ibdata1、数据库子目录、undo 文件,还有一个 xtrabackup_checkpoints,这是后续增量备份和恢复的核心参考文件。
3.2 全量备份的 prepare 到底做了什么
很多新手容易犯一个错误:把备份目录当成可用的数据目录,直接拷贝过去启动 MySQL。这个过程在 MySQL 8.0 下大概率起不来。原因在于 xtrabackup 备份出来的文件不是“干净关闭”的数据文件,它包含了备份期间产生的未刷盘数据,需要先做崩溃恢复型加工。
这个加工过程就是 --prepare。
xtrabackup --prepare \ --target-dir=/backup/mysql/full/2025-06-01_02-00/prepare 阶段 xtrabackup 会回放 redo log,把数据文件恢复到崩溃一致状态。你可以把备份文件想象成“系统崩溃瞬间的磁盘快照”,prepare 就是模拟一次开机时的崩溃恢复,把未提交事务回滚掉,已提交但没刷盘的数据补写进去。
注意,prepare 之后的备份目录就不再适合直接追加增量备份了。如果你打算在这个全量基础上做增量,必须保留 prepare 之前的原始状态。所以常规操作路径是:先做完所有增量备份合并,最后统一做一次 prepare。如果你是先 prepare 再合并增量,后面必须用 --apply-log-only 做特殊处理,这块我会在增量章节展开。
3.3 备份后的 binlog 定位
物理备份恢复后,业务可能还有少量需要追回的数据,这部分靠 binlog。xtrabackup 备份时会自动记住两个关键位点:binlog 文件名和 pos 点,在 xtrabackup_binlog_info 文件里。
这个文件内容类似:
mysql-bin.000012 3456789恢复后要继续同步业务,最基本的方式是使用 mysqlbinlog 解析该位点之后的 binlog 并重放。如果配合主从复制,建新从库时 CHANGE MASTER TO 用的 binlog 文件名和 pos 也在这里。
这里有个细节:从库备份时,binlog 位点信息可能不只一个文件,而是一个 GTID 范围集合。GTID 模式下,更好用的方式是直接读取 xtrabackup 生成的 xtrabackup_binlog_pos_innodb,它包含完整的 GTID 集合,配置复制维度用这个更稳妥。
4. 增量备份实操:流程、合并与自动化
4.1 第一次增量备份怎么跑
增量备份依赖两个输出:
- 上一次全量备份的 xtrabackup_checkpoints 中的 to_lsn
- 上一次增量备份(如果有)的文件变更状态
xtrabackup 自身会处理这些依赖,我们只需要指定基准目录。下面示例是在全量备份基础上,生成一个增量备份:
xtrabackup --backup \ --target-dir=/backup/mysql/inc/2025-06-02_02-00 \ --incremental-basedir=/backup/mysql/full/2025-06-01_02-00 \ --user=backup_user \ --password='StrongPass123'这里 --incremental-basedir 指向的是上一个全量备份目录,不是 prepare 之后的目录,也不是备份文件压缩包。xtrabackup 会读取 basedir 的 checkpoints 文件,拿到基准 LSN,然后扫描当前实例的数据页,把超过这个 LSN 的页面拷贝到 inc 目录。
如果还要做第二次增量,比如第二天凌晨又跑一次,那 basedir 应该指向上一次增量备份目录:
xtrabackup --backup \ --target-dir=/backup/mysql/inc/2025-06-03_02-00 \ --incremental-basedir=/backup/mysql/inc/2025-06-02_02-00 \ --user=backup_user \ --password='StrongPass123'增量链就像搭积木,全量是第一块,每个增量层叠在前一块之上,不能乱序。
4.2 合并增量链并完成最终 prepare
增量备份完成后,数据文件仍然是分散的,必须按顺序合并到全量备份目录。合并过程用 --prepare 加 --apply-log-only 参数。所谓 apply-log-only,就是只做 redo log 回放,不做回滚操作。因为增量备份的数据页可能还依赖于后续增量,如果提前把未提交事务回滚掉,后续增量就没有办法正确合并了。
先合并第一个增量到全量:
xtrabackup --prepare \ --apply-log-only \ --target-dir=/backup/mysql/full/2025-06-01_02-00 \ --incremental-dir=/backup/mysql/inc/2025-06-02_02-00再合并第二个增量:
xtrabackup --prepare \ --apply-log-only \ --target-dir=/backup/mysql/full/2025-06-01_02-00 \ --incremental-dir=/backup/mysql/inc/2025-06-03_02-00全部增量合并完之后,最后做一次不带 --apply-log-only 的 prepare,让整个备份集进入可恢复状态:
xtrabackup --prepare \ --target-dir=/backup/mysql/full/2025-06-01_02-00这里要特别注意:合并多个增量时,顺序必须跟实际备份顺序一致,否则会出现数据错乱。我一般会在备份脚本里把增量目录按时间排序,宁可多写几行判断,也不要手拼命令。
4.3 自动化备份脚本示例
把全量和增量串进 crontab 是最常见的做法。下面这个脚本我精简过,但核心检查逻辑都在。
#!/bin/bash # 简单全量/增量备份脚本,可根据实际环境调整 BACKUP_BASE=/backup/mysql DATE=$(date +%F_%H-%M) USER=backup_user PASS='StrongPass123' HOST=127.0.0.1 PORT=3306 # WEEKDAY 用来判断周一做全量,其余做增量 WEEKDAY=$(date +%u) if [ "$WEEKDAY" -eq 1 ]; then xtrabackup --backup \ --target-dir=${BACKUP_BASE}/full/${DATE} \ --user=${USER} --password=${PASS} \ --host=${HOST} --port=${PORT} else # 找最近的增量目录;没有增量则用全量目录 LAST_INC=$(ls -1d ${BACKUP_BASE}/inc/* 2>/dev/null | tail -1) if [ -z "$LAST_INC" ]; then BASE_DIR=$(ls -1d ${BACKUP_BASE}/full/* | tail -1) else BASE_DIR=$LAST_INC fi xtrabackup --backup \ --target-dir=${BACKUP_BASE}/inc/${DATE} \ --incremental-basedir=${BASE_DIR} \ --user=${USER} --password=${PASS} \ --host=${HOST} --port=${PORT} fi脚本本身不复杂,但有两个点必须处理:一是磁盘空间检查,我实际使用中会在备份前用 df 判断剩余空间,小于阈值直接发告警退出;二是备份完成后把 checkpoints 信息写入日志,方便事后确认 LSN 是否正常增长。这两个点不写进去,自动化跑久了迟早会出事。
5. 恢复过程完整演练
5.1 从备份到可启动实例的完整流程
恢复的核心就是把 prepare 好的备份目录还原到 MySQL 数据目录,然后启动实例。听起来简单,但细节决定成败。
先说快速验证流程。如果只是想验证备份是否可用,可以在新实例上用以下步骤:
- 准备一个新的数据目录,比如 /data/mysql_restore。
- 用 xtrabackup --copy-back 把备份文件复制过去:
xtrabackup --copy-back \ --target-dir=/backup/mysql/full/2025-06-01_02-00 \ --datadir=/data/mysql_restore- 确保数据目录的属主和权限是 mysql 用户。
chown -R mysql:mysql /data/mysql_restore- 启动 MySQL,并指定新数据目录。
这里有个容易踩的坑:MySQL 8.0 的 redo log 文件路径和大小有严格要求。如果用 copy-back 到全新数据目录,xtrabackup 会帮你处理 redo log 文件。但如果手动复制备份目录去覆盖一个已有实例的数据目录,一定要先停掉源 MySQL,并且清空目标目录中的 redo log 文件(通常是 #innodb_redo 目录),否则会报 redo log 不匹配错误。
5.2 恢复到原实例:停库、覆盖、启动
生产环境最常见的场景是数据误操作后,用最近备份恢复到原实例。步骤一般是这样:
- 停掉 MySQL 实例,确保进程完全退出。
- 备份现有数据目录,以防恢复过程中出现二次问题。
- 清空或移走原数据目录下的 InnoDB 文件,注意不要把 binlog 文件和配置目录删了,否则会丢失恢复后的追日志能力。
- 使用 copy-back 或直接 rsync 把 prepare 好的备份目录拷回数据目录。
- 修正属主和权限,启动 MySQL。
我在实际操作中,更倾向于使用 rsync 而不是 copy-back,原因在于 rsync 可以断点续传,而且可以精确控制要保留和覆盖的文件。对于超大数据量的恢复,copy-back 一旦中断就得重来,rsync 起码能接着跑。
rsync -avP /backup/mysql/full/2025-06-01_02-00/ /data/mysql_data/启动后第一件事是查错误日志,重点确认 InnoDB 是否正常完成恢复流程,查看当前 GTID 或 binlog 位点是否跟备份信息一致。
5.3 恢复到新实例:迁移与从库场景
做迁移或者搭从库时,不需要在备份目录上执行 prepare 吗?严格来说,主库迁移时通常直接使用 prepare 后的备份目录。但如果只是搭一个新从库,推荐使用 --apply-log-only 加 --redo-only 参数,让备份处于“可继续合并增量”的状态,然后再做成数据目录。但是这个流程相对进阶,这里优先讲最稳妥的整体还原路径。
从库搭建最关键的是要把主库位点信息设对。拿到 xtrabackup_binlog_info 中的 binlog 文件名和 pos,或者 GTID 集合后,在主库执行:
CHANGE MASTER TO MASTER_HOST='主库IP', MASTER_USER='repl_user', MASTER_PASSWORD='password', MASTER_LOG_FILE='mysql-bin.000012', MASTER_LOG_POS=3456789; START SLAVE;如果是 GTID 模式,设置 gtid_purged 或直接通过 MASTER_AUTO_POSITION=1 配置。这里非常关键的一点是:必须在从库尚未开启复制前设置好 gtid_purged,否则复制会包含已经发生过的交易,导致主从数据错位。
6. 常见问题与排查实录
6.1 备份阶段报错
备份失败的信息五花八门,但真正高频的问题就那么几类。我整理了一个速查表:
| 报错特征 | 可能原因 | 处理建议 |
|---|---|---|
| Can't create/write to file | 备份目录权限或空间不足 | 用 df -h 检查空间,chown 备份用户 |
| Access denied for user | MySQL 账号权限不足 | 重新授权,特别关注 BACKUP_ADMIN 和 RELOAD |
| space is low | 磁盘满 | 清理旧备份,备份前检查磁盘 |
| xtrabackup can't open redo log | 目标实例正在运行且未配置正确 | 确认是否连接了错误实例,不要备份临时目录 |
| version check failed | 版本不匹配 | 仔细确认 MySQL 与 xtrabackup 大版本一致 |
还有个问题很隐蔽:备份过程中 MySQL 负载突然飙升,xtrabackup 的 redo log copy 阶段会感知到后端线程落后,从而决定是继续等待还是终止备份。如果业务写入压力极大,备份经常失败,可以增加 --throttle 参数限制 IO 吞吐,或者把备份错峰到业务低峰期。
6.2 prepare 与恢复阶段的典型问题
prepare 阶段最常见的报错是 “Could not find valid redo log”,这类报错通常发生在备份不完整,或者你把全量备份的目录误认为已经 prepare 过。还有一种情形是增量合并顺序错了,导致回放失败。
恢复阶段的报错要更隐蔽一些。有一次我在新机器上恢复,MySQL 启动时报 “The redo log was created before MySQL 8.0.30”。排查半天发现备份目录里的 redo log 源文件来自不同小版本节点,而我直接物理覆盖到了新版本实例上,兼容性检查没通过。于是老老实实重新用 xtrabackup 的 clean 方式处理一次,问题才解决。
数据文件权限也是高频坑。MySQL 以 mysql 用户运行,但备份和恢复操作如果用了 root 执行,很容易把数据目录权限搞成 root:root,启动时直接报权限错误。恢复完必须统一执行 chown。
6.3 恢复后数据一致性的快速校验
恢复完成不是终点,一致性校验才是。我每次恢复后至少做三件事:一是查 error log,确认 InnoDB 回滚完成且没有 “corruption” 字样;二是对比备份时记录的行数和位点,比如用 innodb_table_stats 查关键表行数;三是随机抽查几张大表数据,与实际业务逻辑核对。
如果用的是 GTID 模式,还可以查 gtid_executed 是否与源端对齐。这一步能发现位点设置错误的隐患,避免复制环境埋雷。
最后再分享两个小技巧
第一个是备份留存策略。我见过不少团队把备份目录堆成山,等到磁盘满了才想起来清理。常规做法是全量保留 2 份,增量保留最近 14 天,过期的备份自动删除。但只删文件还不够,还要定期做一次“恢复测试”,否则备份是否有效永远是未知数。我通常每周选一个较小实例完整走一遍备份加恢复流程,确认整个链路畅通。
第二个技巧是备份期间的监控。xtrabackup 跑起来之后,用 iostat 看磁盘 IO 压力,用 innotop 或 performance_schema 看数据库负载。如果发现备份过程把业务拖垮,可以通过调整 --parallel 和 --throttle 参数控制线程数和限速。默认并行度并不一定适合每台机器,我一般先在测试环境压一遍,找出既不拖垮业务、又能跑完备份的最佳值,再固化到脚本里。
备份这件事,平时感觉不到价值,真正需要它的时候往往是在救火现场。把 xtrabackup 的备份、增量、prepare、恢复这几步彻底跑顺,提前演练几次,你会在关键时刻比别人从容很多。