ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MySQL异地备份方案:从凌晨宕机事故到恢复演练全攻略

MySQL异地备份方案:从凌晨宕机事故到恢复演练全攻略 凌晨2点40分手机震动把我吵醒监控告警显示主库宕机了。我眯着眼睛爬起来准备用凌晨1点自动跑的定时备份来恢复。结果登录备份服务器一看本地磁盘满了今天的备份文件只写了3GB就中断了而循环清理策略已经把前几天可用的全量备份删掉了。那一刻我非常清醒脑子里只有四个字数据丢了。那次事故之后我把数据库本地定时备份改成了数据库异地定时备份。这短短几个字的差别背后多出的东西不少备份策略重新设计、工具选型重新评估、异地传输链路搭建、定时任务从单机cron改成跨机房调度再加上每月一次的恢复演练。这篇文章把我整套方案的思路、踩过的坑、以及脚本细节整理出来希望对正在规划MySQL备份方案的同学有帮助。1. 一个让我凌晨三点起来做异地备份的真实事故1.1 监控告警响起的那个凌晨先说当时的状况。主库宕机原因是磁盘IO被一个慢查询拖垮加上连接数被打满最终实例僵死。这种故障其实不难处理——重启实例、清理慢查询、限流恢复真正麻烦的是重启之后发现数据文件有损坏InnoDB日志回放失败单靠本地备份又恢复不了。那天的全量备份定时任务确实执行了但备份文件不完整备份盘前一天被同事导了一批测试数据占满了备份脚本在写文件到一半时因磁盘耗尽退出。更尴尬的是清理策略里设置只保留最近2份全量凌晨1点那次的备份文件因为不完整核心标记缺失没被正式算进保留列表前一天的旧备份又被自动清掉了。结果就是本地没有一份完整可用的全量备份。本地备份失效的典型场景有几个磁盘写满、备份脚本中途失败、循环清理误删、机房断电导致本地存储损坏。这些风险有一个共同点——它们和主库故障往往是一根藤上的瓜。同一机房一断电主库挂了备份机也挂在同一个机柜里谁也救不了谁。这就是异地备份存在的根本原因你需要一份在主服务器物理距离之外的数据副本。1.2 RPO和RTO把能丢多少数据和多久恢复翻译成人话在设计备份方案之前先要把两个指标想清楚不然方案做到一半很容易跑偏。RPORecovery Point Objective恢复点目标。它回答的是你能容忍丢多少数据。如果RPO是24小时那么你的备份频率至少不能低于每天一次理想情况是每天全量备份一次实时地把binlog等增量数据同步走。我见过很多团队说我们每天凌晨2点全量备份但RPO却写在24小时这种配置其实是刚刚达标一旦白天出问题当天从凌晨2点到故障时间点的所有数据全没了。RTORecovery Time Objective恢复时间目标。它回答的是业务可以断多久。如果RTO是4小时那么从故障发生到业务恢复整个手动流程走完就不能超过4个小时。这意味着恢复步骤必须脚本化、可重复执行不能靠某位同事现场敲命令临场发挥。很多人会把RPO和RTO搞混我提供一个简单记法RPO是往画面后退看你丢掉了多少帧RTO是按下播放键之后多久能恢复到能继续看。备份频率由RPO决定恢复自动化程度由RTO决定。1.3 为什么本地备份永远不够本地备份作为第一道防线是必须有的它恢复速度最快IO走内网不用跨公网拉数据。但本地备份只能算防止误删、覆盖的保险它挡不住机房级故障。我后来给自己定了一个底线原则任何一份关键数据至少要存活在两个物理位置并且两个位置之间的网络要独立。异地备份不需要追求与主库机房毫秒级同步它只需要做到主库全挂了在另一个城市还能找到一份相对完整、可以恢复的数据。备份传输走公网也行走专线更好甚至人工拷盘都算一种极端兜底方案总之与故障域隔离。异地备份还有一个隐性好处防勒索攻击。这几年不少勒索事件里攻击者会先在内网潜伏一阵子甚至把备份服务器一起加密。异地备份如果放在独立的云对象存储或另一套账号体系下攻击路径被截断恢复数据就还有最后一张底牌。2. 备份策略设计全量、增量、binlog怎么搭配才是最优解2.1 全量备份、增量备份、binlog日志分别解决什么问题先看全量备份。它是在某个时间点对数据库做一份完整快照可能是逻辑层面的SQL文本也可能是物理层面的数据文件拷贝。全量备份是恢复的地基无论增量机制怎么设计最终都要落到一个全量点上。没有全量备份你手里的binlog再多也没有基础可以往上回放。增量备份这个概念在MySQL社区里有两种理解。第一种是xtrabackup等工具提供的LSN增量备份备份自上次备份以来变化过的数据页物理层面节省存储空间和网络流量。第二种是全量备份binlog日志回放的组合binlog记录了全量备份之后每一个变更操作恢复时把全量备份先恢复再把binlog按时间点回放就相当于补上了全量之后的所有数据。我个人的偏好是大多数场景不需要碰xtrabackup的增量直接定期全量binlog持续归档更简单、更可靠。原因后面工具选型部分细说。binlog是MySQL的二进制日志记录的是所有数据变更事件数据定义语言/数据操作语言。它本质上是一份流水账记录了所有写操作有了它你可以做时间点恢复。开启binlog只需要在配置里加上log-bin和server_id两个参数。2.2 我推荐的组合方案全量binlog回放结合RPO/RTO的要求我现在的固定组合是每周一次全量备份周日凌晨2点每天同步binlog到异地binlog日志每5分钟拉取一次。这个方案下RPO最好的情况接近5分钟最坏情况也就是5分钟内的数据变更。对于绝大多数业务系统这个水平已经远超RPO不超过24小时的要求。每周的全量持续binlog恢复时以最近一份全量为起点回放从全量备份时刻开始的binlog就能恢复到故障前最后一刻。举个例子周日凌晨2点做了全量备份。到了周三下午4点数据库出问题你需要做的恢复动作是先把周日的全量备份恢复到一台新实例然后把周日2点之后到周三4点之间的binlog日志依次回放。binlog日志是按mysql-bin.000001这种序列编号的恢复时逐个应用即可。这套方案最大的优势是全量备份间隔长备份操作对线上业务的影响频次低但是数据恢复的空间很小——binlog的持续归档保证了数据的连续性。2.3 保留周期与磁盘成本怎么平衡备份文件很占空间尤其是物理备份一份几百GB的库做全量备份压缩之后体积也不小。我通常建议双份保留本地保留最近2份全量备份加上1天binlog满足快速恢复需求。异地保留最近4份全量备份加上30天猫的binlog满足长时间回溯和审计需求。为什么本地只需要2份因为全量备份的保留价值主要在恢复点2份可以防止正在写入的那一份损坏。而异地保留更长是考虑到容灾场景下你可能需要追溯到更早之前的问题比如数据被篡改、业务逻辑bug引入了脏数据这时候30天binlog会让你有足够的回放窗口去定位问题发生点。为了省空间传输到异地之前要做压缩。全量备份用gzip或者zstd压缩binlog用mysqlbinlog输出原始格式后在文件层面压缩。需要注意的是mysqldump生成的SQL文件本身压缩率很高通常能压缩60%-80%xtrabackup物理备份压缩率相对低一些但胜在恢复时不用先解压整个文件再导入各有取舍。3. 工具选型mysqldump、Xtrabackup、binlog到底选哪个3.1 mysqldump逻辑备份适合中小规模mysqldump是MySQL自带的逻辑备份工具它把数据导出成一组INSERT/CREATE语句。优点是跨版本兼容性好、文件可读性强、恢复时不需要MySQL大版本完全一致缺点是导出和导入都很慢适合单实例数据量在10GB以下、最多到几十GB的场景。用mysqldump做InnoDB表备份有两个参数是必加的mysqldump -u backup_user -p \ --single-transaction \ --routines \ --triggers \ --events \ --master-data2 \ --all-databases /backup/full_$(date %F).sql--single-transaction保证备份基于InnoDB的一致性快照不加的话直接锁表线上业务会被打爆。--master-data2会把当前的binlog文件名和位置记录在SQL文件里注释形式呈现恢复之后你能知道该从哪条binlog继续回放。有一个实用技巧mysqldump输出的文件末尾会有一行-- Dump completed on ...恢复之前先grep一下这个关键词能提前发现文件不完整的问题。3.2 xtrabackup物理备份大库利器数据库到500GB、1TB这个量级mysqldump导出要跑几个小时恢复时导入又要几个小时RTO根本压不下来。这时候要用xtrabackupPercona开源的物理备份工具直接拷贝数据文件速度非常快而且备份过程对在线业务影响小。核心命令xtrabackup --backup \ --target-dir/backup/xtra/$(date %F) \ --host127.0.0.1 \ --userbackup_user \ --passwordxxx做完备份之后还要执行prepare阶段xtrabackup --prepare --target-dir/backup/xtra/$(date %F)prepare的作用是把备份期间产生的redo日志回放到数据文件中让数据文件达到一个一致的可用状态。这一步必须在恢复到新实例之前完成在备份机上做可以节省恢复时间。物理备份的恢复方式一般是直接把整个目录拷贝到数据目录下然后启动MySQL不需要source导入的过程所以恢复速度快。它还有一个衍生用途在主从架构里用xtrabackup备份主库数据去初始化一个新的从库比mysqldump导入快得多热词里linux下xtrabackup备份mysql主库、部署从库、GTID同步方式讲的基本就是这条链路。3.3 binlog每天的流水账增量数据的核心binlog本身不是备份工具但它决定了你的数据能回放到多细的粒度。开启方式见下面配置[mysqld] server_id 1 log-bin mysql-bin binlog_format ROW expire_logs_days 7binlog_format建议用ROW。STATEMENT格式记录的是SQL语句日志体积小但有些操作比如NOW()函数、触发器等回放结果可能不一致ROW格式记录的是每一行数据的变化日志更大但恢复更精确。现在的MySQL默认就是ROW不要改回STATEMENT。expire_logs_days控制本地binlog保留时长它是一个清理阈值不是归档机制——超过阈值的binlog会被自动删除。所以你需要另外把binlog文件同步到异地后再清理否则丢的就是不可恢复的数据。binlog到底怎么同步走最简单的方式是用rsync直接把mysql-bin.*文件同步到另一台机器另一种方式是使用mysqlbinlog工具远程拉取但脚本要处理断点记录复杂度高一些。实际生产里我用的是前者——文件级别的同步简单、可审计、不容易出错。3.4 不同数据量级的选型建议我把这些经验整理成一张表直接对着选就好数据量级推荐全量方案增量/持续归档恢复策略10GB以下mysqldump每日全量binlog每日同步解压SQL导入binlog回放10GB-200GBmysqldump每周全量binlog每5分钟同步全量导入binlog回放200GB-1TBxtrabackup每周全量binlog每5分钟同步物理目录恢复binlog回放1TB以上xtrabackup备份主从binlog实时同步到异地从库提级或物理恢复这里有一个容易陷入的误区总想用xtrabackup的增量备份功能。我的建议是除非全量备份一次的时间已经长到影响业务比如几个小时的窗口你都找不到否则不要用增量。理由有三个增量链越长链上任何一个文件损坏后面所有增量都前功尽弃prepare增量链时处理顺序必须严格容错很低维护成本比全量binlog高但收益并不明显。4. 异地传输备份文件真正到达异地才叫异地备份4.1 rsync增量同步最简单可靠的方案跨机房传输备份文件我的首选工具一直是rsync。它不是把文件全量拷到对端而是用滚动校验算法只传差异部分配合--partial参数传输中断之后重新执行会从断点继续不会从头再来。基本命令rsync -avz --partial \ /backup/xtra/ \ backup10.x.x.x:/remote_backup/xtra/-a保留文件属性-z开启压缩--partial保留部分传输文件。配合SSH密钥免密登录完全可以写在crontab里无人值守。这里有一个关键细节rsync默认遇到目标端已存在的文件会跳过但你新建了当天的备份目录的话不会有冲突。另外记得先传文件之后再传一个校验文件比如md5sum计算的结果目标端可以通过比对校验文件判断这份备份传完整没有。4.2 对象存储容量与地域容灾的平衡除了自建异地服务器把备份传到对象存储云厂商的OSS/S3/COS这类服务也是一个主流选择。对象存储的优势是容量基本不需要担心而且有跨区域复制功能你传到一个地域它自己在另一个地域生成副本地域容灾能力很强。直接用一个简单的curl或CLI命令就能上传# 以某云厂商CLI为例其他云平台大同小异 ossutil cp /backup/xtra/$(date %F).tar.gz oss://my-backup-bucket/xtra/上传之前先本地压缩减小传输体积、节省存储费用。完整命令tar czf /backup/xtra_$(date %F).tar.gz /backup/xtra/如果对传输过程的数据安全性有要求在压缩时加一层加密或者在文件上传前用openssl加密处理。上传命令里也不要把AccessKey明文写在命令行里通过环境变量传入。在我实际维护的多个项目里我的做法是自建对象存储双通道自建异地服务器上存放7天内的快速恢复备份对象存储存放30天以上的归档备份。恢复的时候优先用最近的异地服务器备份速度最快时间久远的归档从对象存储拉取也不至于没有数据可用。4.3 一个完整的备份传输脚本长什么样为了让你有东西可以直接落地我贴一份我线上在用的简化版脚本。它同时覆盖了全量备份、压缩、本地清理、rsync传输、失败告警五个环节#!/bin/bash # mysql_remote_backup.sh set -e BACKUP_DIR/backup/xtra TODAY$(date %Y%m%d_%H%M%S) FULL_BACKUP_DIR${BACKUP_DIR}/${TODAY} REMOTE_HOSTbackup10.x.x.x REMOTE_DIR/remote_backup/xtra # 1. 全量备份 /usr/local/bin/xtrabackup --backup \ --target-dir${FULL_BACKUP_DIR} \ --host127.0.0.1 \ --userbackup_user \ --password${DB_PASS} # 2. prepare让备份处于一致可用状态 /usr/local/bin/xtrabackup --prepare --target-dir${FULL_BACKUP_DIR} # 3. 压缩tar gzip tar czf ${FULL_BACKUP_DIR}.tar.gz -C ${BACKUP_DIR} ${TODAY} # 4. 清理临时目录 rm -rf ${FULL_BACKUP_DIR} # 5. 保留本地最近2份超出删除 ls -1d ${BACKUP_DIR}/*.tar.gz | head -n -2 | xargs rm -f # 6. 增量文件binlog每5分钟同步这个脚本不重复处理只同步全量 rsync -avz --partial ${FULL_BACKUP_DIR}.tar.gz \ ${REMOTE_HOST}:${REMOTE_DIR}/ # 7. 远程保留最近4份 ssh ${REMOTE_HOST} ls -1d ${REMOTE_DIR}/*.tar.gz | head -n -4 | xargs rm -f echo $(date %F %T) backup and transfer done: ${TODAY} /var/log/mysql_backup.log脚本里有几个细节我解释一下set -e表示任何一条命令失败就退出避免看起来执行了其实没备份成功的情况。加了--prepare再压缩保证备份文件到达异地之后直接解压就能用于恢复。先tar临时目录再删除防止半成品混进正式备份目录。远程清理命令通过ssh调执行注意双引号内的$要转义否则会在本地展开。binlog的同步脚本结构类似找到每天新生成的binlog文件rsync到异地指定目录然后在本地执行PURGE BINARY LOGS BEFORE NOW()来释放磁盘空间。5. 定时任务编排别再手动跑脚本了5.1 Linux crontab表达式背后的几个坑Linux上定时任务首选crontab。全量备份我安排在每周日凌晨2点binlog同步每5分钟一次0 2 * * 0 /opt/mysql_remote_backup.sh */5 * * * * /opt/mysql_binlog_sync.sh写cron的时候有几个坑我必须提醒第一个坑是环境变量。cron的任务不是运行在你的登录shell里PATH往往只有/usr/bin:/bin你用/usr/local/bin下的工具比如xtrabackup就必须写全路径或者脚本开头先source /etc/profile加PATH。第二个坑是并发。全量备份可能要跑一小时如果某个操作拖过了cron触发点下一次任务会叠加执行。我的做法是给脚本加一个锁文件exec 9/var/lock/mysql_backup.lock flock -n 9 || exit 0flock的作用是如果前一次任务还在跑后一次任务直接放弃本次执行避免两个备份进程同时写目录。第三个坑是日志。crontab默认只在命令报错时发一封邮件到本机邮箱没人会去看。脚本里一定要自己把日志写到文件并且对关键步骤的输出做处理。日志至少包含开始时间、结束时间、备份文件大小、是否成功、失败时的错误信息。5.2 Windows定时任务与任务计划程序Windows环境下MySQL运维同学经常需要处理的是Windows Server上的MySQL实例。定时任务用系统自带的任务计划程序就能搞定。命令行创建任务的语法是schtasks /create /tn MySQLRemoteBackup /tr C:\backup\mysql_backup.bat /sc weekly /d SUN /st 02:00更省事的方式直接在图形界面里建任务触发器选按预定计划操作选启动程序指向你的.bat批处理脚本再加一个重要的条件设置——如果任务运行时间超过XX天则停止取消勾选避免任务挂起之后占着资源。Windows脚本里要注意的细节mysqldump或xtrabackup的路径要带引号程序名不要只写mysqldump要写完整路径备份输出的时间戳用%date%可能有格式歧义推荐在bat里显式格式化日期或者直接调用PowerShell脚本做日期处理。5.3 告警与日志备份失败必须第一时间知道定时任务最怕的就是一直没有告警直到出问题才发现备份已经失败一个月了。告警机制必须作为备份方案的一部分而不是事后补丁。我常用的告警链路是脚本执行结束后根据set -e的返回码和日志文件大小决定是否调用一个通用的告警函数把消息推送到值班群的webhook。简单说就是脚本末尾追加一段检测逻辑if [ -f /var/log/mysql_backup.log ]; then SIZE$(stat -c%s /var/log/mysql_backup.log) if [ $SIZE -lt 100 ]; then curl -s -X POST -H Content-Type: application/json \ -d {content:MySQL backup log maybe missing, check immediately} \ https://your-monitor-webhook.local fi fi更规范的做法是把备份状态写到一个状态文件由监控系统定期拉取检查。比如备份成功后写入last_backup_timexxx监控系统如果发现当前时间减去上次备份时间超过预设阈值就触发告警。这样即使备份脚本本身没报错但是因为某种原因任务根本没有执行也能被发现。日志里的关键字段我用表格列出来你可以直接照抄这个标准字段含义示例timestamp执行时间2025-06-15 02:00:00backup_type全量/增量/binlogfullfile_name备份文件full_20250615.tar.gzfile_size备份文件大小12.3Gduration耗时1h 05mstatus备份状态success/failedremote_status异地同步状态synced/failed6. 恢复演练与监控异地备份好不好用模拟一次灾难就知道6.1 备份文件完整性的日常校验备份做完了不代表数据一定可用。文件完整性和数据可用性完全是两码事文件完整可能因为备份过程中MySQL还在写、逻辑备份中途报了警告等问题导致恢复出来不一致。日常校验我做了三件事第一mysqldump文件检查尾部标记。用grep Dump completed验证导出过程完整结束。第二xtrabackup备份目录做prepare后用innodb_checksum_algorithm验证或启动一个临时实例执行CHECK TABLE做基本校验。第三校验备份文件大小变化。如果某天备份文件大小突然比平时小很多大概率是备份中途丢数据了需要人工介入确认。这些校验可以写成另一个定时任务每天跑一次结果合并到日志和告警系统。6.2 季度恢复演练从零搭建一个临时实例并恢复我认为所有备份方案里最有价值的动作是真刀真枪的恢复演练。半年一次已经算懒的推荐每季度至少一次。演练的流程大概是准备一台临时服务器安装与生产相同大版本的MySQL。从异地备份服务器拉取最近一份全量备份。如果是逻辑备份source导入如果是物理备份解压后拷贝到数据目录启动MySQL。把全量时刻之后的binlog回放直到最新位置。对关键业务表做select count(*)和源库比对行数确认数据量级一致。我建议演练时必须记录时长从拉取备份到恢复完成一共花了多久。这个数字就是你的真实RTO。如果远超业务要求的4小时就要优化恢复流程比如把物理备份的目录直接放到更快的数据盘、binlog回放并行化等。还有一个小建议演练不要挑业务空闲期偷偷摸摸做尽量在非生产环境按照标准流程完整走一遍最好让同事盯着看比自己一个人操作更能暴露问题。6.3 备份失败排查清单最后整理一份排查清单总结这些年在备份问题上遇到的高频故障和解决方法直接对着查故障现象可能原因排查与解决备份文件明显偏小备份中途报错自动退出检查日志报错段看是磁盘满还是锁冲突prepare阶段报redo日志缺失备份期间MySQL异常关闭重新做一次完整备份不要尝试用损坏的备份恢复rsync传到一半断了网络不稳定、带宽被占满加--partial参数断点续传考虑错峰传输binlog回放时报错binlog文件不连续或损坏检查源端binlog文件列表确认没有跳号备份任务没执行cron环境变量或目录权限问题手动跑脚本看报错检查cron日志异地上传后文件无法解压传输过程中文件损坏用校验文件比对在源端重新生成校验值备份时间异常长主库压力大、网络拥塞调整备份窗口或改用物理备份工具备份这块还有一个容易被忽视的点MySQL升级或者大版本变更之后旧的备份脚本可能直接失效。比如从5.7升到8.0mysqldump的认证插件兼容性、xtrabackup版本匹配都有讲究。每次变更之后先手动跑一次全流程不要等定时任务自己去撞坑。最后说一个我自己的小习惯每次改完备份脚本我会在测试实例上故意插入几行测试数据跑一次全量备份再模拟删除其中一部分然后用备份脚本恢复到删除前的时间点。整个过程全自动执行不需要人工干预验证通过才把脚本合入生产。数据库备份不是一个配好了就不用管的事情每一次变更、每一次架构调整都应该逼自己重新走一遍完整的备份和恢复链路。
返回列表