4380 字
22 分钟
MySQL 高可用详解(3)数据备份

前两篇解决的是”节点故障”:复制、仲裁、自动切换。但误删数据、逻辑写坏、勒索加密、机房租约到期这类事故,复制会忠实地把错误同步到所有副本。真正兜底的只有一件事——离线且可验证的备份。

基础知识#

同城/异地灾备#

架构数据副本位置RPORTO成本适用
单机房同机房多副本秒级(同步)/ 分钟级(异步)分钟级低起步阶段,但机房级故障即业务中断
同城双机房同城 A/B 机房秒级分钟级中绝大多数生产系统的最低要求
异地灾备(两地三中心)同城双活 + 异地异步副本同城秒级 / 异地分钟级同城分钟级 / 异地小时级中高金融、政企等合规要求
异地多活多地均可读写取决于同步方式秒级切换高全国性业务,需解决数据冲突与就近路由

从故障域角度看:

  • 同城:应对机房单点故障
  • 异地:应对区域性自然灾害,拥有独立电网/供应商骨干光缆

从网络 RTT角度看:

  • 同城机房通常 RTT < 2ms,可以用半同步甚至同步(MGR/Galera)复制,RPO 接近 0;
  • 跨地域 RTT 普遍 20~50ms,同步复制会把每次提交都拖慢,只能异步复制,RPO 通常是秒级到分钟级,且必须接受”故障时丢最后一小段事务”。

多活的关键约束:任何需要跨地域强一致的写入都无法低延迟完成。因此多活一般按”数据单元(用户/地域)“切分流量,让绝大部分写入在单元内闭环,只有少量全局数据(如全局唯一 ID、全局配置)走跨单元同步。

容灾目标#

衡量容灾能力的几个指标:

指标含义决定因素
RPO(Recovery Point Objective)能容忍丢失多长时间的数据备份周期、是否归档 binlog、复制是否同步
RTO(Recovery Time Objective)从故障到业务恢复的目标时长检测速度、切换/恢复自动化程度、数据量大小、是否热备节点
MTTR平均修复时长,实际值演练质量、工具链、文档完备度
MTBF平均无故障时间架构冗余度、硬件质量

几个容易忽略的点:

  • RPO ≠ 备份间隔:即使每天全备一次,只要归档了 binlog,RPO 就能做到秒级(恢复到任意时间点)。备份是”基线”,binlog 是”增量胶片”。
  • RTO 由最慢的一环决定:数据量 500GB 时,解压 + --prepare + 拷贝 datadir 的耗时可能是小时级,热备节点会快得多。
  • 实际值必须靠演练测出来,写在文档里的 RTO 是愿望,跑过一遍的才是能力。

容灾的分层设计(越往下越”冷”,也越便宜):

层级手段恢复速度数据新鲜度
1主从/多副本自动切换秒~分钟秒级
2binlog 归档 + 时间点恢复分钟~小时秒级
3物理全备 + 增量(xtrabackup)小时上次备份时刻
4逻辑备份(mysqldump)小时~天上次备份时刻
5离线异地冷备(对象存储/磁带)天更早

这五层要同时存在,因为它们是针对不同类型事故的:切换对节点故障,时间点恢复对误删,冷备对”备份本身被加密/被删”。

数据备份与恢复#

基于mysqldump#

逻辑备份:把数据导出成 SQL 文本。优点是通用、可读、单表可恢复、跨版本兼容;缺点是慢、占空间、恢复慢、导出时对实例有压力。

全量逻辑备份#

Terminal window
mysqldump \
--single-transaction \ # InnoDB 一致性快照(不锁表)
--source-data=2 \ # 注释形式写入 binlog 位点(8.0.26 前的写法是 --master-data=2)
--set-gtid-purged=ON \ # 记录 GTID 信息,便于重建复制
--routines --events --triggers \
--hex-blob \
--default-character-set=utf8mb4 \
--all-databases \
| gzip > /backup/mysql-all-$(date +%F).sql.gz

关键参数解释:

参数作用
--single-transaction开启可重复读事务获取一致性快照,仅对 InnoDB 有效;MyISAM 表仍会锁
--source-data=2(旧名 --master-data=2)把 binlog 坐标以注释写入文件顶部,用于时间点恢复
--set-gtid-purged=ON / OFF / AUTOON 写入 SET @@GLOBAL.gtid_purged,是重建从库时的关键;仅做归档、要导入到已有集群时用 OFF 避免 GTID 冲突
--single-transaction + --lock-all-tables二者互斥,不能同时使用
--routines --events --triggers默认不含存储过程/函数、事件、触发器,必须显式加
--hex-blob二进制列以十六进制导出,避免乱码
--where="..." / --tables只导出指定条件/表,用于小范围恢复

增量备份#

mysqldump 本身没有增量能力,所谓”逻辑增量”要靠两种手段:

  1. binlog 归档:备份只做基线,之后持续归档 binlog,恢复时”全备 + 重放 binlog”到任意时间点。
Terminal window
# 持续归档 binlog:--raw 保留二进制原格式,--stop-never 读到文件末尾后不退出
# 注意:它不会自动跟进主库新建的 binlog 文件,通常配合循环/定时任务在文件轮转后重启
mysqlbinlog --read-from-remote-server --host=10.0.0.11 --user=repl --password='...' \
--raw --stop-never --result-file=/backup/binlog/ mysql-bin.000003
  1. 按变更列做差量导出:适合大表的部分恢复,例如只导出某时间之后的行。
Terminal window
mysqldump --single-transaction --no-create-info mydb orders \
--where="updated_at >= '2026-09-01 00:00:00'" > orders-delta.sql

恢复与时间点恢复(PITR)#

Terminal window
# 1. 恢复基线
gunzip < /backup/mysql-all-2026-09-17.sql.gz | mysql -uroot -p
# 2. 用 binlog 重放到故障前一刻(按时间)
mysqlbinlog --start-datetime='2026-09-17 09:00:00' \
--stop-datetime='2026-09-17 10:29:59' \
/backup/binlog/mysql-bin.000010 | mysql -uroot -p
# 或按位点
mysqlbinlog --start-position=4096 --stop-position=88512 /backup/binlog/mysql-bin.000010 | mysql -uroot -p
-- GTID 模式下改用 GTID 区间,更精确
SET GTID_NEXT = '3f2b1c8e-...-9a01:1001'; -- 单条
-- 或
SET GLOBAL gtid_purged = '3f2b1c8e-...-9a01:1-1000';

误删事故的标准处理顺序:

  1. 立即停止写入(或把业务切到从库),避免错误继续扩散;
  2. 定位误操作的起始 GTID / 时间点(mysqlbinlog 里搜 DROP TABLE 等关键字);
  3. 在临时实例上恢复基线 + 重放 binlog 到误操作前一刻;
  4. 从临时实例中把受损对象导出,再导入生产(比整库回滚影响小得多);
  5. 复盘:把权限(禁止生产直连 DDL)、sql_safe_updates、回收站式删除等措施补上。

基于xtrabackup#

物理备份:直接拷贝 InnoDB 的数据文件,热备、不锁表、速度快,是生产环境大库的首选。

全量备份#

Terminal window
# 备份:流式 + 并行压缩,直接写到目标目录(也可管道给远端)
xtrabackup --defaults-file=/etc/my.cnf \
--backup \
--user=backup --password='BackupPassw0rd!' \
--target-dir=/backup/xtrabackup/full \
--binlog-info=ON \
--parallel=4 --compress --compress-threads=4
# 检查备份信息
cat /backup/xtrabackup/full/xtrabackup_checkpoints
# backup_type = full-backuped
# from_lsn = 0
# to_lsn = 26654213
# last_lsn = 26654222

要点:

  • xtrabackup_checkpoints 里的 to_lsn 是后续增量备份的起点,备份完成后立刻记下来(连同 xtrabackup_binlog_info 里的 binlog 位点/GTID)。
  • --binlog-info=ON 会在备份目录写入 xtrabackup_binlog_info,用于建立复制。
  • 备份期间 redo log 与数据文件同时在变,所以备份目录本身不是可启动的数据目录,必须先 --prepare 做崩溃恢复。

增量备份#

增量备份只拷贝上次备份之后被修改过的页,因此必须有清晰的全备基线。

Terminal window
# 1. 全备(基线)
xtrabackup --backup --user=backup --password=... \
--target-dir=/backup/xtrabackup/base
# 2. 增量 1,基于基线
xtrabackup --backup --user=backup --password=... \
--target-dir=/backup/xtrabackup/inc1 \
--incremental-basedir=/backup/xtrabackup/base
# 3. 增量 2,基于增量 1(增量可以链式叠加)
xtrabackup --backup --user=backup --password=... \
--target-dir=/backup/xtrabackup/inc2 \
--incremental-basedir=/backup/xtrabackup/inc1

恢复准备(prepare)的顺序不能错:

Terminal window
# 全备:只回放 redo,不提交(--apply-log-only 保持可继续叠加增量)
xtrabackup --prepare --apply-log-only --target-dir=/backup/xtrabackup/base
# 依次叠加每个增量,中途都必须 --apply-log-only
xtrabackup --prepare --apply-log-only --target-dir=/backup/xtrabackup/base \
--incremental-dir=/backup/xtrabackup/inc1
# 最后一个增量:去掉 --apply-log-only,做完整的崩溃恢复(回滚未提交事务)
xtrabackup --prepare --target-dir=/backup/xtrabackup/base \
--incremental-dir=/backup/xtrabackup/inc2

恢复(Restore)#

Terminal window
# 1.(若压缩过)解压 + 解压后 prepare
xtrabackup --decompress --parallel=4 --target-dir=/backup/xtrabackup/base
xtrabackup --prepare --target-dir=/backup/xtrabackup/base
# 2. 停库并清空 datadir(先确认没有需要保留的表空间!)
systemctl stop mysqld
mv /var/lib/mysql /var/lib/mysql.broken.$(date +%F_%H%M) # 保留现场,便于事后排查
mkdir -p /var/lib/mysql
# 3. 拷贝回 datadir(--copy-back 保留备份;--move-back 直接移动、更快但备份即消失)
xtrabackup --copy-back --target-dir=/backup/xtrabackup/base
# 4. 修正属主(必须!否则 mysqld 无法启动且报错不直观)
chown -R mysql:mysql /var/lib/mysql
# 5. 启动并校验
systemctl start mysqld
mysql -e "SELECT COUNT(*) FROM mydb.orders; SHOW MASTER STATUS;"

流式与直传对象存储#

大库备份最怕”先落本地再上传”——本地磁盘不够、窗口太长。xbstream + xbcloud 可以让备份直接流到远端:

Terminal window
# 流式备份:通过 SSH 直接传到备份机
xtrabackup --backup --stream=xbstream --parallel=4 --user=backup --password=... \
| ssh backup@10.0.0.20 "xbstream -x -C /backup/xtrabackup/streamed"
# 直传对象存储(OSS/S3/GCS)
xtrabackup --backup --stream=xbstream --user=backup --password=... \
| xbcloud put --storage=s3 --s3-bucket=my-db-backup \
--s3-endpoint=https://oss-cn-hangzhou.aliyuncs.com \
--s3-access-key=... --s3-secret-key=... full-2026-09-17

从库备份与复制重建#

在从库上做物理备份是最常见的生产做法:避免影响主库,且备份天然包含复制信息。

Terminal window
# 从库备份时暂停复制、等 SQL 线程追平,备份完成后自动恢复
xtrabackup --backup --safe-slave-backup --slave-sql-channel=... \
--target-dir=/backup/xtrabackup/repbase

用备份重建一个从库的关键一步是对齐 GTID:

Terminal window
# 备份目录中的 GTID 信息
cat /backup/xtrabackup/repbase/xtrabackup_binlog_info
# mysql-bin.000012 1954 3f2b1c8e-...-9a01:1-1050
-- 恢复后的实例上执行:告诉它"这些事务已经在数据里了"
RESET MASTER;
SET GLOBAL gtid_purged = '3f2b1c8e-...-9a01:1-1050';
CHANGE REPLICATION SOURCE TO
SOURCE_HOST='10.0.0.11', SOURCE_PORT=3306,
SOURCE_USER='repl', SOURCE_PASSWORD='...',
SOURCE_AUTO_POSITION=1;
START REPLICA;
SHOW REPLICA STATUS\G -- 等 Seconds_Behind_Master 归零

备份校验:备份不校验等于没有备份#

校验层次做法
文件级--prepare 是否成功、xtrabackup_checkpoints 是否完整、压缩包能否解压
内容级xtrabackup --datadir=... --stats 检查页校验和,或 innochecksum
实例级在临时实例上真正恢复一次并启动,跑几条业务查询(这才是唯一可信的验证)
度量级记录备份耗时、大小、恢复耗时,纳入监控

mysqldump 与 xtrabackup 怎么选#

维度mysqldumpxtrabackup
备份类型逻辑(SQL 文本)物理(数据文件)
速度 / 数据量慢,超过几十 GB 就很痛苦快,适合 TB 级
锁与影响--single-transaction 下影响小,但 MyISAM 会锁表热备,几乎无锁
恢复速度慢(要重放 SQL)快(拷贝文件即可)
跨版本 / 跨平台兼容性好版本必须严格匹配
单表/单行恢复方便需借助表空间导出(transportable tablespace)或临时实例
典型用途小库、单表恢复、数据迁移、逻辑归档大库全量/增量、重建从库、实例克隆

实践中的组合是:xtrabackup 做全量 + 增量 + binlog 归档(负责大范围恢复),mysqldump 做小范围与逻辑迁移(负责精细恢复)。

容灾演练#

场景:数据库主从结构(单主单从),xtrabackup工具,从库全量备份。

演练流程#

备份流程各阶段:Pre-flight checks → 流式备份 → Verify backup → 推远端存储 → Cleanup old local backups → 设置cronjob

阶段目标关键检查点
Pre-flight checks确认”这次备份值得做”MySQL 进程与端口可达、备份账号权限充足、datadir 剩余空间足够、上次备份是否成功(失败要先止损)、当前是否处于维护窗口
流式备份边备份边传输,不落满本地盘记录起止时间、备份大小、xtrabackup_checkpoints 的 to_lsn、binlog/GTID 位点;备份前后复制延迟是否有异常抬升
Verify backup证明这份备份”能用”--prepare 是否成功、checkpoint 是否完整、压缩包是否可解、必要时抽样恢复到临时实例验证
Push to OSS把备份送出本机/本机房上传是否完整(比对大小与校验和)、远端路径是否带日期与实例标识、是否开启生命周期规则与版本控制(防误删)
Cleanup old local backups控制本地磁盘占用只保留最近 2 份本地备份(满足快速回滚),删除前确认远端已有完整副本
设置 cronjob让流程无人值守地重复失败告警、日志留存、并发锁(避免上一次没跑完就被第二次触发)、随机化启动时间避免与业务高峰/其他定时任务撞车

容灾演练各阶段:校验前提 → 列出可用备份 → 下载/解压/准备备份 → 停数据库 → 重命名datadir模拟灾难 → 恢复备份 → 启动 MySQL + 验证 → 重建复制

阶段目标关键检查点
校验前提演练所需的凭据与变量齐备数据库密码已设置(可通过环境变量/凭据管理读取)、远程存储的 endpoint/bucket/AK 等变量已设置、演练环境与生产隔离
列出可用备份选一份”最近且完整”的备份远端清单按时间倒序,确认对应备份的校验结果与 to_lsn/GTID 位点,记录本次演练使用的备份标识
下载 / 解压 / 准备备份把备份变成”可直接使用的数据目录”下载完整性校验、解压 --decompress、--prepare 是否成功;这一步在停库之前完成,可最大限度缩短停机窗口
停数据库释放 datadir 并冻结现场先停应用写入或确认演练环境无流量,优雅停库(避免强杀造成二次损坏),确认无残留进程占用文件
重命名 datadir 模拟灾难制造”原数据不可用”的真实故障只重命名不删除(便于回滚与事故分析)、确认新 datadir 为空、记录重命名后的一切异常,检验流程对”真灾难”的假设是否成立
恢复备份把准备好的数据搬回 datadir用 --copy-back(保留备份目录以便复用)/ --move-back,修正属主与权限,确认文件数量与大小合理
启动 MySQL + 验证证明恢复后的实例真的可用启动是否报错、错误日志是否有 InnoDB 恢复信息、核心表行数与抽样记录是否合理、业务关键查询能否跑通、GTID 位点是否与预期一致
重建复制让恢复出的实例重新回到高可用拓扑用备份中的 GTID 设置 gtid_purged、指向当前主库、确认 Replica_IO/SQL_Running=Yes 且延迟归零,最后把从库重新纳入读写分离与备份轮换

演练之后#

演练的价值不在”跑通了”,而在于把发现的每一个卡点变成改进项:

  • 记录实测 RTO / RPO(从”宣布故障”到”业务可用”的墙上时钟),与目标对比;
  • 梳理卡点:下载慢、解压耗时、凭据找不到、属主忘记改、复制起不来——每一项都要在自动化脚本里修掉;
  • 演练脚本化、脚本版本化,与备份脚本放在同一个仓库,任何架构变更都必须回归演练;
  • 固定节奏:核心库每季度一次全流程演练,每月一次恢复启动验证(只做”能否启动 + 抽样查询”,几分钟即可完成)。

小结#

  • 复制解决”节点故障”,备份解决”数据本身出问题”。两者缺一不可,且备份必须离线、异地、可验证。
  • RPO 由”备份周期 + binlog 归档”决定,RTO 由”恢复方式的自动化程度”决定;两个数字都必须来自实测演练。
  • mysqldump 适合小库与精细恢复,xtrabackup 适合大库的热备全量/增量与实例克隆;生产中两者通常并用,并用 binlog 归档补齐时间点恢复能力。
  • 备份的唯一可信校验方式是真恢复一次;--prepare 成功不等于备份可用。
  • 至此三篇闭环:主从复制保住数据副本与读扩展(第 1 篇)→ 故障检测与自动切换保住可用性(第 2 篇)→ 备份与容灾演练保住数据本身(第 3 篇)。三者共同构成 MySQL 高可用的完整答案。
Round-Trip TimeRound-Trip TimeRound-Trip Time
MySQL 高可用详解(3)数据备份
https://blog-l7wd3.pages.dev/posts/ha/mysql-dr-backup/
作者
L7WD3-Xiao
发布于
2026-09-07
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取