前两篇解决的是”节点故障”:复制、仲裁、自动切换。但误删数据、逻辑写坏、勒索加密、机房租约到期这类事故,复制会忠实地把错误同步到所有副本。真正兜底的只有一件事——离线且可验证的备份。
基础知识
同城/异地灾备
| 架构 | 数据副本位置 | RPO | RTO | 成本 | 适用 |
|---|---|---|---|---|---|
| 单机房 | 同机房多副本 | 秒级(同步)/ 分钟级(异步) | 分钟级 | 低 | 起步阶段,但机房级故障即业务中断 |
| 同城双机房 | 同城 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 | 主从/多副本自动切换 | 秒~分钟 | 秒级 |
| 2 | binlog 归档 + 时间点恢复 | 分钟~小时 | 秒级 |
| 3 | 物理全备 + 增量(xtrabackup) | 小时 | 上次备份时刻 |
| 4 | 逻辑备份(mysqldump) | 小时~天 | 上次备份时刻 |
| 5 | 离线异地冷备(对象存储/磁带) | 天 | 更早 |
这五层要同时存在,因为它们是针对不同类型事故的:切换对节点故障,时间点恢复对误删,冷备对”备份本身被加密/被删”。
数据备份与恢复
基于mysqldump
逻辑备份:把数据导出成 SQL 文本。优点是通用、可读、单表可恢复、跨版本兼容;缺点是慢、占空间、恢复慢、导出时对实例有压力。
全量逻辑备份
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 / AUTO | ON 写入 SET @@GLOBAL.gtid_purged,是重建从库时的关键;仅做归档、要导入到已有集群时用 OFF 避免 GTID 冲突 |
--single-transaction + --lock-all-tables | 二者互斥,不能同时使用 |
--routines --events --triggers | 默认不含存储过程/函数、事件、触发器,必须显式加 |
--hex-blob | 二进制列以十六进制导出,避免乱码 |
--where="..." / --tables | 只导出指定条件/表,用于小范围恢复 |
增量备份
mysqldump 本身没有增量能力,所谓”逻辑增量”要靠两种手段:
- binlog 归档:备份只做基线,之后持续归档 binlog,恢复时”全备 + 重放 binlog”到任意时间点。
# 持续归档 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- 按变更列做差量导出:适合大表的部分恢复,例如只导出某时间之后的行。
mysqldump --single-transaction --no-create-info mydb orders \ --where="updated_at >= '2026-09-01 00:00:00'" > orders-delta.sql恢复与时间点恢复(PITR)
# 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';误删事故的标准处理顺序:
- 立即停止写入(或把业务切到从库),避免错误继续扩散;
- 定位误操作的起始 GTID / 时间点(
mysqlbinlog里搜DROP TABLE等关键字); - 在临时实例上恢复基线 + 重放 binlog 到误操作前一刻;
- 从临时实例中把受损对象导出,再导入生产(比整库回滚影响小得多);
- 复盘:把权限(禁止生产直连 DDL)、
sql_safe_updates、回收站式删除等措施补上。
基于xtrabackup
物理备份:直接拷贝 InnoDB 的数据文件,热备、不锁表、速度快,是生产环境大库的首选。
全量备份
# 备份:流式 + 并行压缩,直接写到目标目录(也可管道给远端)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做崩溃恢复。
增量备份
增量备份只拷贝上次备份之后被修改过的页,因此必须有清晰的全备基线。
# 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)的顺序不能错:
# 全备:只回放 redo,不提交(--apply-log-only 保持可继续叠加增量)xtrabackup --prepare --apply-log-only --target-dir=/backup/xtrabackup/base
# 依次叠加每个增量,中途都必须 --apply-log-onlyxtrabackup --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)
# 1.(若压缩过)解压 + 解压后 preparextrabackup --decompress --parallel=4 --target-dir=/backup/xtrabackup/basextrabackup --prepare --target-dir=/backup/xtrabackup/base
# 2. 停库并清空 datadir(先确认没有需要保留的表空间!)systemctl stop mysqldmv /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 mysqldmysql -e "SELECT COUNT(*) FROM mydb.orders; SHOW MASTER STATUS;"流式与直传对象存储
大库备份最怕”先落本地再上传”——本地磁盘不够、窗口太长。xbstream + xbcloud 可以让备份直接流到远端:
# 流式备份:通过 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从库备份与复制重建
在从库上做物理备份是最常见的生产做法:避免影响主库,且备份天然包含复制信息。
# 从库备份时暂停复制、等 SQL 线程追平,备份完成后自动恢复xtrabackup --backup --safe-slave-backup --slave-sql-channel=... \ --target-dir=/backup/xtrabackup/repbase用备份重建一个从库的关键一步是对齐 GTID:
# 备份目录中的 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 怎么选
| 维度 | mysqldump | xtrabackup |
|---|---|---|
| 备份类型 | 逻辑(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 高可用的完整答案。
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 49 天
部分内容可能已过时