☰
数据库高可用与容灾实战(6):备份体系设计:mysqldump、XtraBackup 与快照的边界
2026/9/28 18:22:34 网站建设 项目流程

备份防的是三种死法

前几篇处理的都是"活着的副本":复制、选举、路由,防的是机器死。但生产数据还有另外两种死法:逻辑死——误删表、坏发布写脏数据、勒索加密,这些会顺着复制链在毫秒内传播到所有从库,副本越多死得越齐;历史死——数据需要回到"某时刻之前",而在线系统只会向前。备份体系防的就是后两种,它也因此成为整个容灾架构里唯一不能被复制替代的组件。本篇把三种主流手段——mysqldump 逻辑备份、XtraBackup 物理备份、文件系统/云盘快照——各自的机制、一致性与时间账算清楚,第 7 篇再讲如何用它们把时间拨回去。

三种备份,各买哪种一致性

mysqldump:一致性快照的 SQL 转写。核心参数--single-transaction:导出前开一个 REPEATABLE READ 事务,借 MVCC 拿到一个冻结的读视图,整个导出过程所见数据都停在这一刻(只适用于事务引擎;导出中途的 DDL 会破坏快照,8.0 应配--tidl思路的替代——禁止并行 DDL 或错开窗口)。--source-data=2(8.0.26 前叫--master-data)在导出文件头部写入CHANGE REPLICATION SOURCE TO注释(文件名+位点,或 GTID 集),这是第 7 篇 PITR 的锚点。它买到的是应用级一致点(跨表外键、守恒关系都对),代价是单线程逐表 SELECT + INSERT 文本,慢、体积大、恢复要重放 SQL 并重建索引。适合十 GB 级、或需要跨版本/跨引擎迁移的场景;mydumper/mysqlsh util.dumpInstance的并行导出缓解了备份侧,恢复侧依旧受限。

XtraBackup:热拷数据页 + redo 尾巴。它绕过 Server 层直接拷 InnoDB 的.ibd/系统表空间,因此不需要冻结 SQL;但页与页的拷贝发生在不同瞬间,天然不是一个一致点。它用两件事把账做平:备份期间持续捕获 redo log,备份结束前做一个检查点;恢复(--prepare)时从检查点 LSN 重放 redo——页比检查点新的记录跳过,比检查点老的补齐,等价于一次定向的崩溃恢复,把散落的时间戳拉回同一终点。所以 XtraBackup 的备份速度接近磁盘裸速、恢复也快一个量级,但它买的只是一致性页面的集合,逻辑错误(把错删也备份进来)照单全收。8.0 时代的主流实现是 Percona XtraBackup。

快照:断电一致性的拷贝。LVM/Storage Spaces/云盘快照在fsfreeze配合下把写入暂停几秒拿到块级一致点;不 freeze 时得到的等价于"突然拔电"——InnoDB 能靠 redo 恢复(crash recovery),但应用层"半执行"的业务状态不受保护。快照的真正价值是速度与时空覆盖:分钟级拿到全库、云快照天然跨区复制、配合 CBT(变化块跟踪)做增量链。它与"备份完成"之间还差一步:快照是实例级而非表级,恢复粒度粗,回滚动作会吞掉快照点之后的全部数据,必须有配套的 binlog 归档兜底。

实验一:500GB 库的时间账

先算账再选型。以下模型按导出/重放/拷贝/回放四段速率估算(估算值,非实机测量),看三种方式的备份窗口与整库恢复时长。

DB_GB,CHURN=500,0.05# 库大小, 日变更比例DUMP_MBPS,REPLAY_MBPS=60,22# mysqldump 导出 / SQL 重放(含建索引)COPY_MBPS,REDO_MBPS=250,400# 物理拷贝带宽 / redo 回放量速db=DB_GB*1024# MBday_redo=db*CHURN# 当日 redo 量(用于物理备份追尾)defhrs(sec):returnsec/3600.0print("库 %dGB, 日变更 %dGB(%.0f%%)"%(DB_GB,day_redo/1024,CHURN*100))dump_bak=db/DUMP_MBPS dump_res=db/REPLAY_MBPSprint("mysqldump: 备份 %.1fh, 整库恢复 %.1fh, RPO 取决于 dump 频率(binlog 另算)"%(hrs(dump_bak),hrs(dump_res)))xb_bak=db/COPY_MBPS+day_redo/REDO_MBPS xb_res=db/COPY_MBPS+day_redo/REDO_MBPSprint("XtraBackup 全量: 备份 %.1fh(热拷页+追redo尾), 恢复 %.1fh(拷回+崩溃恢复)"%(hrs(xb_bak),hrs(xb_res)))lvm_freeze=2.0lvm_bak=lvm_freeze+db/COPY_MBPS lvm_res=db/COPY_MBPSprint("LVM/云盘快照: 冻结 %.0fs + 后台拷贝 %.1fh, 恢复需 crash recovery %.1fh+"%(lvm_freeze,hrs(db/COPY_MBPS),hrs(lvm_res*0.2)))print("\n把 RPO 补齐靠 binlog 连续归档, 与备份方式无关:")pitr=xb_res+db*0.02/REDO_MBPS/60print(" PITR 追加成本 = 距上次全量的 binlog 重放时长; 全量间隔越长, RTO 越长, 这是备份策略的核心权衡")print("\n压缩与传输(dump 侧收益最大): gzip 后 mysqldump 体积约 %.0f%%, 异地传输带宽 100MB/s 时:"%25)print(" dump 传输 %.1fh -> 压缩后 %.1fh; 物理备份本身已近磁盘裸速, 压缩主要省存储不省时间"%(hrs(dump_bak),hrs(dump_bak)*0.6))

运行输出:

库 500GB, 日变更 25GB(5%) mysqldump: 备份 2.4h, 整库恢复 6.5h, RPO 取决于 dump 频率(binlog 另算) XtraBackup 全量: 备份 0.6h(热拷页+追redo尾), 恢复 0.6h(拷回+崩溃恢复) LVM/云盘快照: 冻结 2s + 后台拷贝 0.6h, 恢复需 crash recovery 0.1h+ 把 RPO 补齐靠 binlog 连续归档, 与备份方式无关: PITR 追加成本 = 距上次全量的 binlog 重放时长; 全量间隔越长, RTO 越长, 这是备份策略的核心权衡 压缩与传输(dump 侧收益最大): gzip 后 mysqldump 体积约 25%, 异地传输带宽 100MB/s 时: dump 传输 2.4h -> 压缩后 1.4h; 物理备份本身已近磁盘裸速, 压缩主要省存储不省时间

这张表读出的不是"谁快谁慢",而是三段的分工:全量备份决定恢复地板(RTO 下限),binlog 归档决定 RPO 上限,两者相乘才等于真实风险敞口。500GB 级别 mysqldump 的 6.5 小时恢复已不可接受,物理备份或快照是唯一选项;而逻辑备份并未出局——它在表级抢救、跨版本迁移、以及"给数仓喂一份可 grep 的数据"这些物理手段到不了的点位上不可替代。成熟体系几乎都是"物理全量 + binlog 连续归档 + 少量逻辑备份做逃生舱"。

实验二:被撕开的账本——不带 redo 的热拷长什么样

把"为什么物理拷贝必须配 redo"做成一个可复算的账:100 个账户每页 100 元、总额守恒 10000;20 笔转账每秒一笔,热拷贝每秒拷一页。拷贝结束后的账面与真相差多少?再从检查点 LSN 重放 redo 看能否收敛。

N_ACCOUNT,INIT=100,100# 每页一个账户, 守恒总额 = 10000ops=[]# (seq, 出账方, 入账方): 20 笔转账forkinrange(20):ops.append((5*(k+1),(3*k)%N_ACCOUNT,(3*k+7)%N_ACCOUNT))naive,page_lsn={},{}forpginrange(N_ACCOUNT):val,lsn=INIT,0forseq,a,binops:ifseq<pg+1and(a==pgorb==pg):val+=-1ifa==pgelse1lsn=seq naive[pg],page_lsn[pg]=val,lsnprint("守恒校验(朴素热拷): 应为 %d, 实际 %d, 差额 %+d 元 -> 跨页事务被撕开"%(N_ACCOUNT*INIT,sum(naive.values()),sum(naive.values())-N_ACCOUNT*INIT))torn=sum(1forseq,a,binopsif(seq<a+1)!=(seq<b+1))print("被撕开的转账: %d / %d 笔(一端页已拷、另一端未拷)"%(torn,len(ops)))final,applied,skipped=dict(naive),0,0forseq,a,binops:forpg,deltain((a,-1),(b,1)):ifpage_lsn[pg]<seq:final[pg]+=delta page_lsn[pg]=seq applied+=1else:skipped+=1print("\nredo 重放: 生效 %d 条页级记录, 跳过 %d 条(页比检查点更新, 天然幂等)"%(applied,skipped))print("守恒校验(redo 追平后): 总额 %d, 差额 %+d -> 所有页停在同一逻辑终点"%(sum(final.values()),sum(final.values())-N_ACCOUNT*INIT))fixed=sum(1forpginnaiveifnaive[pg]!=final[pg])print("修正了 %d 个'拷早了'的页, 其余 %d 页原样可用"%(fixed,N_ACCOUNT-fixed))print("\n边界: LVM/云盘快照 = 断电一致性(等价 crash, 靠 crash recovery 收敛, 不指定应用一致点)")print("fsfreeze 防的是页内撕裂, --single-transaction 防的是跨页撕账——热备必须两样都有")

运行输出:

守恒校验(朴素热拷): 应为 10000, 实际 10002, 差额 +2 元 -> 跨页事务被撕开 被撕开的转账: 2 / 20 笔(一端页已拷、另一端未拷) redo 重放: 生效 38 条页级记录, 跳过 2 条(页比检查点更新, 天然幂等) 守恒校验(redo 追平后): 总额 10000, 差额 +0 -> 所有页停在同一逻辑终点 修正了 38 个'拷早了'的页, 其余 62 页原样可用 边界: LVM/云盘快照 = 断电一致性(等价 crash, 靠 crash recovery 收敛, 不指定应用一致点) fsfreeze 防的是页内撕裂, --single-transaction 防的是跨页撕账——热备必须两样都有

最值得注意的是"钱凭空多出来"的方向:撕开的转账里入账端被拷进、出账端没拷进,备份账面比现实多出两元。这类静默的守恒破坏不会让任何文件报错,只有在恢复后跑对账才能暴露——这解释了为什么备份体系必须内建恢复验证,而不是"备份任务绿灯 = 数据安全"。redo 重放的幂等性(LSN 已追过的记录直接跳过)正是 XtraBackup--prepare的语义内核,也是"随便什么顺序拷页都能收敛"的底气。

体系落地清单

  • 全量频率按"恢复时长 = 全量恢复 + binlog 重放"倒推:RTO 预算 4 小时,500GB 库就绝不允许一周一次全量。
  • binlog 归档独立于备份任务:连续、带确认(第 3 篇的 binlog server 顺手兼任),断流要告警——RPO 全靠它。
  • 遵守 3-2-1:3 份副本、2 种介质、1 份离线/不可变(对象存储 WORM/冷备),勒索软件最先删的就是在线备份目录。
  • 每季度随机抽一个备份做真恢复:恢复到临时实例、跑CHECK TABLE、抽样对账(守恒关系、行数、checksum),并把 RTO 实测值记录在案;没恢复验证过的备份等于没有备份。
  • 备份要带"坐标":全量文件头记录对应的 GTID 集/binlog 位点(--source-data、xtrabackup_info、START-SLAVE-POINTER),没有坐标的备份进不了 PITR 链条。
  • 表级逃生通道:确认 mysqldump 单表导出/导入演练过(含外键与触发器),误删单表的场景等不起整库恢复。

备份有了、坐标有了,下一篇把它用起来:《数据库高可用与容灾实战(7):PITR 时间点恢复:把误删的数据找回来》——如何用一份全量加两天空白日志,把被DROP TABLE的账本精确恢复到误删前一秒。

参考来源

  • MySQL 8.0 Reference Manual:Database Backup Methods:https://dev.mysql.com/doc/refman/8.0/en/backup-methods.html
  • MySQL 8.0 Reference Manual:mysqldump:https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html
  • Percona XtraBackup 8.0 Documentation:https://docs.percona.com/percona-xtrabackup/8.0/en/introduction.html
  • Wikipedia:Snapshot (computer storage):https://en.wikipedia.org/wiki/Snapshot_(computer_storage)
  • Wikipedia:Backup:https://en.wikipedia.org/wiki/Backup

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询