有一句运维圈的玩笑话很实在:"没有做过恢复演练的备份,等于没有备份。"真实的灾难往往发生在半年没关注过备份的那个人值班的夜里:磁盘阵列坏了、rm -rf 执行了、或者一条没有 WHERE 的 UPDATE 成功了。备份方案的价值不在"有没有",而在 RTO(恢复时长目标)和 RPO(可容忍的数据丢失量)能不能达标。本文围绕 mysqldump、Percona XtraBackup 与 MySQL 8.0 的 Clone 插件三条主线,给出可直接使用的命令、增量/时间点恢复流程与一套恢复验证方法。
一、先选方案:逻辑备份还是物理备份
| 方案 | 类型 | 备份速度 | 恢复速度 | 优点 | 局限 |
|---|---|---|---|---|---|
| mysqldump | 逻辑 | 慢(单线程) | 慢(需重放 SQL) | 自带、跨版本兼容、可筛表筛数据、文本可 grep | 大库耗时长;不含 binlog 位置时需额外记录 |
| mydumper | 逻辑 | 快(多线程) | 快(myloader 多线程) | 多线程、输出压缩、一致性快照 | 需额外安装 |
| XtraBackup(PXB) | 物理 | 快(文件拷贝) | 快(prepare 后直接用) | 支持增量、支持压缩加密、不停机 | 版本需与 MySQL 匹配;只能整机/整实例 |
| Clone Plugin(8.0.17+) | 物理 | 快 | 快 | 官方内置、可远程克隆给新实例 | 克隆期间捐赠者只读,且目标实例会被清空 |
| LVM / 存储快照 | 物理 | 极快 | 快 | 对数据库几乎无侵入 | 需 LVM 或支持快照的存储 |
一个实用的选择基准:数据量小于 50GB 用 mysqldump 足够简单;50GB 到 1TB 强烈建议 Percona XtraBackup 或 mydumper;TB 级且追求分钟级 RTO 需要考虑物理备份加延迟从库组合。
二、mysqldump 实战
2.1 一条标准的全量备份命令
mysqldump -h127.0.0.1 -uroot -p'密码' \ --single-transaction \ --master-data=2 \ --routines --triggers --events \ --default-character-set=utf8mb4 \ --hex-blob \ --flush-logs \ --set-gtid-purged=ON \ --all-databases | gzip > /backup/full_$(date +\%F).sql.gz
逐个解释关键选项:
- --single-transaction:对 InnoDB 开启一个一致性读事务,全程不加表锁,业务可读可写。这是热备份的基础。注意:它只对 InnoDB 有效,如果库里还有 MyISAM 表,必须配合
--lock-all-tables,否则非事务表的数据会不一致。 - --master-data=2:在输出文件里以注释形式写入
CHANGE MASTER TO MASTER_LOG_FILE=..., MASTER_LOG_POS=...,搭建从库或做时间点恢复时全靠它。=1是不注释的可执行语句(导入时会自动 change master),一般只用 2。 - --routines / --triggers / --events:默认不带存储过程、触发器、事件调度器,漏了会导致恢复后业务报错。
- --set-gtid-purged=ON:启用 GTID 的实例建议保留(默认 AUTO),如果只想做数据迁移不想保留 GTID 信息则用 OFF。
- --hex-blob:二进制字段以十六进制输出,避免特殊字符导致导入失败。
- --flush-logs:切换 binlog,让备份文件与 binlog 的边界更清晰。
2.2 常见的其他用法
# 只导出一张表的部分数据(按条件分片导出) mysqldump -uroot -p appdb orders --where="id > 100000 AND id <= 200000" --single-transaction > orders_part2.sql # 只导出表结构,不含数据 mysqldump -uroot -p --no-data appdb > appdb_schema.sql # 导出时不锁表且不想记录 binlog 位置(纯迁移场景) mysqldump -uroot -p --single-transaction --set-gtid-purged=OFF --master-data=0 appdb > appdb.sql # crontab 定时全备(注意 % 在 crontab 中要转义) 0 2 * * * /usr/bin/mysqldump -uroot -p'密码' --single-transaction --master-data=2 \ --routines --triggers --events -A | gzip > /backup/full_$(date +\%F).sql.gz
2.3 恢复
# 压缩备份 zcat /backup/full_2025-01-01.sql.gz | mysql -uroot -p # 或先解压再导入 gunzip < /backup/full_2025-01-01.sql.gz | mysql -uroot -p appdb # 大文件导入提速(会话级关闭双一) mysql -uroot -p -e "SET GLOBAL innodb_flush_log_at_trx_commit=0; SET GLOBAL sync_binlog=0; SET GLOBAL foreign_key_checks=0;" mysql -uroot -p < /backup/full_2025-01-01.sql mysql -uroot -p -e "SET GLOBAL innodb_flush_log_at_trx_commit=1; SET GLOBAL sync_binlog=1; SET GLOBAL foreign_key_checks=1;" # 查看备份文件里记录的 binlog 位置 zcat /backup/full_2025-01-01.sql.gz | head -30 | grep -i "CHANGE MASTER"
三、Percona XtraBackup:物理全备与增量
XtraBackup 通过拷贝 InnoDB 数据文件并回放 redo log 来保证一致性,备份速度接近文件复制速度,是 TB 级库的首选。
3.1 全量备份
# 创建专用备份账号(最小权限) CREATE USER 'xtra'@'localhost' IDENTIFIED BY 'Str0ng_P@ss'; GRANT BACKUP_ADMIN, PROCESS, RELOAD, LOCK TABLES, REPLICATION CLIENT ON *.* TO 'xtra'@'localhost'; GRANT SELECT ON performance_schema.* TO 'xtra'@'localhost'; # 全量备份 xtrabackup --backup \ --user=xtra --password='Str0ng_P@ss' \ --target-dir=/backup/xtra/full_$(date +\%F) \ --parallel=4 --compress --compress-threads=4 # prepare:把备份里的 redo 应用到数据文件,使其达到一致状态(必须做) xtrabackup --prepare --target-dir=/backup/xtra/full_2025-01-01 # 恢复:目标数据目录必须为空,且 mysqld 已停止 systemctl stop mysqld rm -rf /var/lib/mysql/* xtrabackup --copy-back --target-dir=/backup/xtra/full_2025-01-01 chown -R mysql:mysql /var/lib/mysql systemctl start mysqld
3.2 增量备份
增量基于已有的全备(或上一个增量),只拷贝变化的页:
# 周一全备 xtrabackup --backup --user=xtra --password='Str0ng_P@ss' --target-dir=/backup/xtra/base # 周二、周三增量 xtrabackup --backup --user=xtra --password='Str0ng_P@ss' \ --target-dir=/backup/xtra/inc1 --incremental-basedir=/backup/xtra/base xtrabackup --backup --user=xtra --password='Str0ng_P@ss' \ --target-dir=/backup/xtra/inc2 --incremental-basedir=/backup/xtra/inc1 # 恢复时的 prepare 顺序:除最后一个增备外都要加 --apply-log-only xtrabackup --prepare --apply-log-only --target-dir=/backup/xtra/base xtrabackup --prepare --apply-log-only --target-dir=/backup/xtra/base --incremental-dir=/backup/xtra/inc1 xtrabackup --prepare --target-dir=/backup/xtra/base --incremental-dir=/backup/xtra/inc2 # 最后一个不加 apply-log-only # 然后 copy-back systemctl stop mysqld rm -rf /var/lib/mysql/* xtrabackup --copy-back --target-dir=/backup/xtra/base chown -R mysql:mysql /var/lib/mysql && systemctl start mysqld
--apply-log-only 的作用是"只应用 redo,不做未完成事务的回滚",因为后续还要叠加新的增量。忘记加会导致增量无法继续应用;最后一个 prepare 必须不加这个参数,否则崩溃恢复阶段不会完成。
四、基于 binlog 的时间点恢复(PITR)
"误删了一张表"或"UPDATE 忘了带 WHERE"是最常见的人为事故。这时候全量备份加 binlog 可以精确恢复到出事前一秒。
# 1) 先确认当前还保留着哪些 binlog mysql -uroot -p -e "SHOW BINARY LOGS;" mysql -uroot -p -e "SHOW VARIABLES LIKE 'binlog_expire_logs_seconds';" # 8.0 起以此参数控制保留时长 mysql -uroot -p -e "SHOW VARIABLES LIKE 'expire_logs_days';" # 5.7 及以前 # 2) 找到误操作的位置(GTID 或 pos) mysqlbinlog --base64-output=decode-rows -v /var/lib/mysql/mysql-bin.000042 | grep -n -B5 -A10 "DROP TABLE" mysqlbinlog --start-datetime="2025-01-10 10:00:00" --stop-datetime="2025-01-10 10:20:00" \ --base64-output=decode-rows -v /var/lib/mysql/mysql-bin.000042 > /tmp/binlog_read.txt # 3) 恢复到一台临时实例,而不是直接动生产 # 先恢复全备,再把 binlog 回放到一个时间段之前 mysqlbinlog --start-position=4 --stop-position=873210 /var/lib/mysql/mysql-bin.000042 | mysql -uroot -p # 4) GTID 环境下更推荐按 GTID 集合回放 mysqlbinlog --include-gtids='3E11FA47-71CA-11E1-9E33-C80AA9429562:1-8932' \ /var/lib/mysql/mysql-bin.000042 | mysql -uroot -p
关键点:永远不要在生产实例上直接回放。正确做法是找一台空白实例恢复全备 + binlog,核对无误后,用 mysqldump 或 pt-table-sync 把目标表捞回生产。
五、恢复验证怎么做:隔离恢复、一致性校验与 RTO 口径
恢复演练的原则只有一条:永远不要在生产实例上验证。正确做法是把备份恢复到一台隔离实例上,确认无误后再决定如何把数据捞回生产。
5.1 用隔离实例做恢复
隔离实例要满足三个条件:独立的数据目录、独立的端口与 socket、以及不允许业务连接。物理恢复时建议先用 --skip-networking 启动,彻底关闭 TCP 监听,只留 socket 供本机验证;同时关闭 binlog(--skip-log-bin)避免这次恢复的写入污染 binlog 或被误传到别的从库。
# 1) 准备一个空目录
mkdir -p /verify/3307/{data,log,tmp}
chown -R mysql:mysql /verify
# 2) 恢复 XtraBackup 备份
xtrabackup --copy-back --target-dir=/backup/xtra/base --datadir=/verify/3307/data
chown -R mysql:mysql /verify/3307/data
# 3) 以隔离方式启动:独立 port/socket、关 TCP、关 binlog、跳过主从自动启动
mysqld --no-defaults \
--datadir=/verify/3307/data \
--port=3307 --socket=/verify/3307/mysql.sock --pid-file=/verify/3307/mysqld.pid \
--skip-networking --skip-log-bin --skip-slave-start \
--log-error=/verify/3307/log/error.log \
--innodb_buffer_pool_size=4G &
# 4) 用 socket 登录验证
mysql -S /verify/3307/mysql.sock -uroot -p -e "SELECT VERSION(); SHOW DATABASES;"
# 5) 逻辑备份的隔离导入同理
mysql -S /verify/3307/mysql.sock -uroot -p < /backup/full_2025-01-01.sql
5.2 恢复后的一致性校验
实例起来了不代表数据正确。至少要做三层校验:对象层(表、视图、存储过程、触发器、事件的数量与生产一致)、行数层(关键业务表的粗校验)、内容层(CHECKSUM TABLE 比对。注意 InnoDB 的 table_rows 是采样估算值,只能做粗校验,精确到行数要用 COUNT(*))。最强的校验是把它挂成从库,用 pt-table-checksum 做全表比对。
# 对象层:表数量核对
SELECT table_schema, COUNT(*) AS tables FROM information_schema.tables
WHERE table_schema NOT IN ('mysql','sys','performance_schema','information_schema')
GROUP BY table_schema;
# 对象层:存储过程、函数、触发器、事件不能漏(mysqldump 需要 --routines --triggers --events)
SELECT COUNT(*) FROM information_schema.routines WHERE routine_schema NOT IN ('sys','mysql');
SELECT COUNT(*) FROM information_schema.triggers;
SHOW EVENT STATUS\G
# 行数层:InnoDB 的 table_rows 为估算,只做粗校验
SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema='appdb';
# 精确校验请 COUNT(*)
SELECT COUNT(*) FROM appdb.orders;
# 内容层
CHECKSUM TABLE appdb.orders;
CHECK TABLE appdb.orders;
# 最强校验:把它挂成从库后做全表一致性比对
pt-table-checksum --user=root --password='密码' --databases=appdb
5.3 RTO 与 RPO 的计时口径
演练记录必须写明从什么时刻开始算,否则数据无法横向比较。建议统一口径:
- RTO(Recovery Time Objective):从"决定执行恢复"开始计时,到"业务可以正常读写该实例"为止。中间包含:申请/准备机器、传输备份文件、
xtrabackup --prepare或 SQL 导入、启动与实例自检、业务校验。很多团队只统计导入时间,导致 RTO 被严重低估。 - RPO(Recovery Point Objective):恢复完成后丢失的数据量对应的时长,等于"最后一次可用备份与最后一次成功事务之间的时间差"。
--master-data=2记录的 binlog 位置与磁盘上残留的 binlog 决定了 RPO 下限。 - 每次演练都要记录:备份文件大小、传输耗时、prepare 耗时、导入耗时、总耗时、数据量。随着数据增长,这些数字应当定期复测(建议每季度一次)。
- 经验参考:逻辑备份的恢复耗时通常是备份耗时的 3-10 倍;物理备份的恢复耗时接近文件拷贝时间加 prepare 时间。这也是大库优先选物理备份的核心原因。
5.4 增量合并命令复核
验证带增量的备份集时,--apply-log-only 的顺序是整个流程最容易出错的地方,标准命令序列如下:
# base = 全备;inc1、inc2 = 两次增量,inc2 是最后一个
# 1) 先对全备做"仅应用日志",不做未完成事务的回滚
xtrabackup --prepare --apply-log-only --target-dir=/backup/xtra/base
# 2) 逐个叠加增量(除最后一个外,都要带 --apply-log-only)
xtrabackup --prepare --apply-log-only --target-dir=/backup/xtra/base \
--incremental-dir=/backup/xtra/inc1
# 3) 最后一个增量:不加 --apply-log-only,让它完成崩溃恢复
xtrabackup --prepare --target-dir=/backup/xtra/base \
--incremental-dir=/backup/xtra/inc2
# 4) 恢复到隔离实例
systemctl stop mysqld
rm -rf /verify/3307/data/*
xtrabackup --copy-back --target-dir=/backup/xtra/base --datadir=/verify/3307/data
chown -R mysql:mysql /verify/3307/data
如果第 2 步忘记加 --apply-log-only,后续增量将无法继续叠加(XtraBackup 会提示 LSN 不匹配);如果第 3 步也加了,实例启动时会多做一次崩溃恢复,多数情况下能自愈但不可依赖。另外注意 XtraBackup 版本必须与 MySQL 大版本匹配(MySQL 8.0 用 PXB 8.0,MySQL 5.7 用 PXB 2.4)。
5.5 演练执行清单
- 每周至少做一次真实恢复演练,把备份恢复到一台与生产同规格的隔离实例。
- 启动后检查错误日志有没有 InnoDB 崩溃恢复相关的告警:
grep -i 'crash recovery\|corrupt' /var/log/mysqld.log。 - 核对对象数量:表数、视图数、存储过程数、触发器数、事件数,与生产
information_schema对比。 - 核对数据一致性:对关键表跑计数和 checksum。
- 启动复制验证:用备份文件里的 CHANGE MASTER 位置把这台实例挂成从库,看是否有 1032/1062 报错。
- 记录本次恢复的真实耗时,与 RTO 目标对比,作为扩容或改方案的依据。
- 把演练结果(成功/失败、耗时、发现的问题)写进运维周报,形成闭环。
# 数据量核对
SELECT table_schema, COUNT(*) AS tables FROM information_schema.tables
WHERE table_schema NOT IN ('mysql','sys','performance_schema','information_schema')
GROUP BY table_schema;
# 表行数核对(InnoDB 的 table_rows 是采样估算,只做粗校验)
SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema='appdb';
# 关键表 checksum 校验
CHECKSUM TABLE appdb.orders;
CHECK TABLE appdb.orders;
# 复制一致性更强校验:pt-table-checksum / pt-table-sync
pt-table-checksum --user=root --password='密码' --databases=appdb
六、备份文件的存放、加密与常见故障
备份本身也是一种资产,同样需要保护。最常见的问题是"备份就在数据库本机那块盘上"——磁盘坏了,数据和备份一起丢失。至少要遵循 3-2-1 原则:三份副本、两种不同介质、一份异地。
6.1 落地规范
- 异地拷贝:全备完成后立刻推到对象存储或另一机房的备份机,用 rsync / rclone 均可,关键是要记录传输结果并对
md5做校验。 - 保留策略:建议保留最近 7 天日备、最近 4 周周备、最近 3 个月月备;策略要写成脚本自动清理,不要靠人工删。
- 权限与加密:备份文件包含全部业务数据,必须
chmod 600;含敏感字段的建议用 openssl 或 gpg 加密后再上传,密钥单独存放。 - 不要把密码写在命令行里:环境变量或
~/.my.cnf(权限 600)里的[client]段更安全,避免被ps或 shell history 看到。
# 用配置文件免密,避免明文出现在命令行 cat > ~/.my.cnf << 'CNF' [client] user=root password=Str0ng_P@ss CNF chmod 600 ~/.my.cnf # 备份后做校验并同步到异地 mysqldump --single-transaction --master-data=2 -A | gzip > /backup/full_$(date +\%F).sql.gz md5sum /backup/full_$(date +\%F).sql.gz > /backup/full_$(date +\%F).md5 rsync -avz --password-file=/etc/rsync.pass /backup/ backup@10.0.9.9::bak/mysql/
6.2 三类典型故障
| 现象 | 原因 | 处理 |
|---|---|---|
| mysqldump 报 Table doesn't exist | 备份过程中另一会话做了 DROP/RENAME,--single-transaction 的一致性快照不含该表 | 重做备份;DDL 避开备份窗口 |
| 从库复制报 1236 Could not find first log file | 主库 binlog 已被 Purge,从库要的位置不存在 | 用最新全备重建从库;调长 binlog_expire_logs_seconds |
| XtraBackup 报 version mismatch | XtraBackup 版本与 MySQL 版本不匹配(如 2.4 无法备份 8.0) | MySQL 8.0 用 PXB 8.0;5.7 用 PXB 2.4 |
6.3 备份期间为什么业务会卡
逻辑备份会执行大量 SELECT,可能把 buffer pool 里的热点页挤出去,导致业务在读高峰期出现抖动;物理备份则是文件级的数据文件拷贝,加上 XtraBackup 在收尾时对 redo 的处理,也会有一定 IO 压力。解决办法是把备份窗口挪到业务低峰,限速限并发(XtraBackup 的 --throttle、--parallel,mydumper 的 --threads、--rows),并且优先在从库上做备份,主库只在万不得已时才承担备份任务。
小结
备份方案的设计起点是 RTO 与 RPO,而不是工具本身:50GB 以内的库用 mysqldump --single-transaction --master-data=2 就能应付;更大的库应该上 XtraBackup 并配周期性的增量 + binlog 做 PITR;痛点在"慢备份恢复"时再考虑 mydumper 和 Clone 插件。避坑清单:不要把备份和数据库放在同一块盘;不要以为 --single-transaction 能保护 MyISAM 表;不要把 binlog_expire_logs_seconds(或 expire_logs_days)设得太短,否则想恢复时 binlog 已经被清理;不要在 XtraBackup prepare 增量时漏掉 --apply-log-only;不要跳过恢复演练;不要给备份脚本里的明文密码设置 644 权限。遵循 3-2-1 原则(3 份副本、2 种介质、1 份异地),绝大多数灾难都能扛过去。
企业QQ咨询




