数据恢复这件事有个残酷的特点:它能不能成功,在事故发生之前就已经决定了。 出事之后你能做的所有事情,都受限于之前做过什么准备。有备份、有演练、有预案,恢复只是按流程走;什么都没准备,那就只能碰运气,而且运气通常不好。
这篇文章讲的是事故发生前的准备工作(备份策略、快照、灾难恢复预案)和事故发生后的正确处置顺序。重点会讲两个部分:一是数据库误删数据的标准恢复流程(停止写入、保护现场、备份加 binlog 时间点恢复),二是文件删除后在没有备份的情况下能做什么——这里会如实说明恢复成功率很低这个事实,以及哪些工具在什么条件下还有一线希望。
一、先把两个指标定下来
所有恢复方案的设计都从这两个数字开始。
RPO(Recovery Point Objective,恢复点目标):允许丢失多长时间的数据。RPO = 5 分钟意味着最坏情况下丢 5 分钟的数据。
RTO(Recovery Time Objective,恢复时间目标):需要多长时间恢复服务。RTO = 30 分钟意味着从故障发生到业务可用要在 30 分钟内完成。
这两个数字决定了方案的复杂度和成本:
| RPO / RTO 要求 | 需要的方案 | 成本 |
|---|---|---|
| RPO 天级、RTO 小时级 | 每天一次全量备份 | 低 |
| RPO 小时级、RTO 小时级 | 每天全量 + 增量备份 | 中 |
| RPO 分钟级、RTO 数十分钟 | 全量 + binlog 持续备份 | 中 |
| RPO 秒级、RTO 分钟级 | 主从热备 + 自动切换 | 高 |
| RPO 0、RTO 秒级 | 同步复制 + 集群自动故障转移 | 很高 |
不要盲目追求 RPO = 0。 同步复制意味着主库每次写入都要等从库确认,这会直接增加写入延迟,且从库故障会导致主库不可用。真正的"零丢失"代价极高,大多数业务并不需要。
定指标时要问业务方一个具体问题:"如果昨天下午 3 点的数据全丢了,你们能接受吗?如果不能,最多能接受丢多久?" 这个问题的答案就是 RPO。同样问"网站挂多久你们能接受"就是 RTO。
二、备份策略:全量 + 增量 + binlog
三种备份的组合
全量备份:某个时间点的完整副本。频率通常是每天一次或每周一次。
增量备份:上次备份以来变化的部分。可以是基于备份工具的增量(xtrabackup 的增量备份),也可以是基于 binlog 的增量。
binlog(二进制日志):持续记录所有数据变更。它是最细粒度的增量,能做到任意时间点恢复。
推荐的组合:每周一次全量 + 每天一次增量 + 持续备份 binlog。 数据量小的可以简化成每天全量 + binlog。
binlog 的备份方式
binlog 是本地文件,机器宕机就没了,所以要持续把它同步走。常见做法:
1. 定时同步到异地(简单)
# 每 5 分钟同步一次 binlog 到备份机或对象存储
rsync -av --delete \
-e "ssh -i /root/.ssh/backup_rsa" \
/data/mysql-bin/ backupuser@备份机:/data/mysql-binlog/
放到 crontab:
*/5 * * * * /opt/scripts/sync_binlog.sh >> /var/log/sync_binlog.log 2>&1
2. MySQL 8.0 的 binlog 自动同步(推荐)
MySQL 8.0.21+ 支持让服务器自己把 binlog 上传到远程:
SET PERSIST binlog_transaction_compression = ON;
更常见的做法是用 mysqlbinlog 的远程实时拉取:
mysqlbinlog --read-from-remote-server --host=主库 --user=repl_user --password=xxx \
--raw --stop-never --result-file=/data/binlog_backup/ mysql-bin.000001 &
--stop-never 让它持续跟随主库,只要有新的 binlog 就拉下来,相当于一个"binlog 备用服务器"。 这是把 RPO 压到秒级的实用手段。
3. 依赖从库
如果已经有主从复制,从库本身就是一份实时副本。但要注意:从库不能防误操作——主库上执行 DROP TABLE,从库会忠实地跟着执行。防误操作要靠延迟从库(CHANGE MASTER TO MASTER_DELAY = 3600,让从库延迟一小时)或者靠 binlog 时间点恢复。
延迟从库是应对误操作的低成本方案:主库上误删了,从库一小时后才会执行到那条语句,你有一小时的时间窗口去从库上把数据捞回来。
3-2-1 原则回顾
- 3 份副本:原始数据 + 至少两份备份
- 2 种介质:本地磁盘 + 对象存储(或另一台机器)
- 1 份异地:物理上分离
异地这一份是防"最后一招"的:机房火灾、误 rm -rf 连同备份一起删、云厂商区域故障、勒索软件加密整个内网。
三、快照和备份不是一回事
这是另一个常见误解。
快照是什么
快照是某个时间点的数据状态记录,通常由存储层(LVM、文件系统、云盘、存储阵列)提供。它记录的是"从快照时刻起,哪些块被修改了"(写时复制,Copy-on-Write),所以创建快照非常快(通常秒级),且初始占用空间很小。
# LVM 快照示例
sudo lvcreate -L 10G -s -n snap_mysql /dev/vg0/lv_mysql
# 用完要删除(快照满了会失效)
sudo lvremove /dev/vg0/snap_mysql
风险提示:LVM 快照会在原卷写入时占用空间来保存旧数据。快照空间写满后快照会失效,且会影响原卷性能。用完必须及时删除,并监控快照空间使用率。
云盘快照:
# 各家云厂商的 CLI 不同,思路一致
# 创建快照前通常要先确保数据一致性
快照与备份的区别
| 维度 | 快照 | 备份 |
|---|---|---|
| 存储位置 | 通常与源数据在同一存储系统 | 独立存储/异地 |
| 创建速度 | 秒级 | 分钟到小时级 |
| 占用空间 | 增量(会随修改增长) | 完整副本 |
| 依赖源数据 | 是(源数据损坏,快照也不可用) | 否 |
| 防硬件故障 | 弱(同一存储系统) | 强 |
| 防误删除 | 可以(回滚到快照点) | 可以 |
| 防机房事故 | 不能 | 能(异地备份) |
| 保留成本 | 长期保留成本高 | 可转低频/归档存储,成本低 |
关键结论:快照是"快速回滚"的工具,不是备份。
最典型的例子:云盘快照存储在同一区域的同一存储集群里。如果那个集群发生严重故障(虽然概率很低),你的源数据和快照可能一起不可用。而如果有人删了整个云账号下的资源,快照通常也会被删。
正确的用法是"快照 + 备份"两条腿:
- 快照用于快速回滚(误操作后几分钟内恢复)
- 备份用于真正的灾难恢复(数据被彻底破坏、机房事故)
一个常见的组合:每天做云盘快照保留 7 天(快速回滚用),同时每天做逻辑备份同步到对象存储保留 30 天(灾难恢复用)。
四、灾难恢复方案的分级
按地理距离
| 方案 | 距离 | 切换时间 | 能防什么 | 成本 |
|---|---|---|---|---|
| 本地高可用 | 同机房 | 秒到分钟 | 单机故障、服务崩溃 | 中 |
| 同城双活/主备 | 同城不同机房 | 分钟级 | 机房故障、断电、网络故障 | 高 |
| 两地三中心 | 同城 + 异地 | 分钟到小时 | 城市级灾难 | 很高 |
| 异地冷备 | 异地 | 小时到天 | 城市级灾难(可接受长 RTO) | 低到中 |
"两地三中心"是金融行业的标准:同城两个中心做双活或主备(低延迟、可同步复制),异地一个中心做异步灾备(防城市级灾难)。对大多数企业来说,这属于过度投入。
中小企业更实际的方案:生产环境在云上 + 备份(全量 + binlog)异步同步到另一个地域的对象存储。RPO 分钟级,RTO 小时级,成本很低。
按就绪程度
| 类型 | 描述 | RTO | 成本 | 适用 |
|---|---|---|---|---|
| 冷备(Cold) | 只有数据和备份,没有运行的环境 | 小时到天 | 低 | 非关键系统、预算有限 |
| 温备(Warm) | 环境已就绪,数据定期同步,切换需人工操作 | 分钟到小时 | 中 | 大多数业务的合理选择 |
| 热备(Hot) | 环境实时运行,数据实时同步,可自动切换 | 秒到分钟 | 高 | 核心业务 |
| 双活(Active-Active) | 两地同时提供服务 | 秒级 | 很高 | 大型互联网业务 |
温备是性价比最高的档位:备机(或备库)一直在跑,数据持续同步,需要时人工切换。它比热备便宜很多(备用资源可以是低配),RTO 又能控制在可接受范围。
注意温备的一个陷阱:备用环境平时没有真实流量,你可能不知道它是否真的可用。必须定期做切换演练,否则真到用时才发现配置漂移、版本不一致、依赖缺失。
一个可执行的 DR 清单
- 主库配置(参数文件)是否已备份并纳入版本管理
- 应用配置、代码、证书是否可快速部署到备机
- 备机是否有足够的资源承载流量(哪怕降配运行)
- DNS 或负载均衡的切换方式是否明确(TTL 是否足够小)
- 谁有权执行切换,联系方式是否最新
- 切换后如何验证数据完整性与业务可用性
- 回切(切回主站)的流程
五、误删数据的应急处理
这是最常发生的场景:一条 DELETE 忘了 WHERE,UPDATE 条件写错,DROP TABLE 执行错了库。
第一步:立即停止写入(最重要)
这是整个流程中最关键的一步,却最常被跳过。
为什么要停:新的写入会与恢复操作冲突,可能覆盖你要找回的数据行(如果 id 被复用),也会让 binlog 位点判断变复杂。
具体做法,按侵入性从低到高:
-- 1. 先锁住相关表(最轻量)
LOCK TABLES orders WRITE;
-- 2. 或者把数据库设为只读
SET GLOBAL read_only = ON;
SET GLOBAL super_read_only = ON; -- 连 SUPER 用户也只读
-- 3. 或者直接停应用服务
sudo systemctl stop your-app
super_read_only 比 read_only 更彻底(后者对 SUPER 权限用户无效)。恢复完成后记得关掉。
同时立刻对当前状态做一次快照或备份,哪怕数据已经被破坏:
mysqldump -uroot -p --single-transaction --all-databases > /tmp/emergency_$(date +%s).sql
这一步的价值是:如果后续的恢复操作搞砸了,你还能回到"刚误删完"的状态重来一遍。保护现场是所有数据恢复的第一原则。
第二步:确定误操作的时间点与范围
有几种方式:
- 应用日志(如果记录了 SQL 或操作时间)
- 慢查询日志 / 通用日志
- 直接翻 binlog
mysqlbinlog --start-datetime='2026-10-10 09:00:00' \
--stop-datetime='2026-10-10 10:00:00' \
--base64-output=DECODE-ROWS -v \
/data/mysql-bin/mysql-bin.000123 > /tmp/binlog_analysis.txt
grep -n -i 'DELETE FROM' /tmp/binlog_analysis.txt | head
grep -n -i 'DROP TABLE' /tmp/binlog_analysis.txt | head
-v 会把 ROW 格式的 binlog 解码成可读的伪 SQL,这是定位误操作的关键。找到那条语句后,记录它所在的 binlog 文件、position 和时间。
第三步:在独立的实例上恢复
绝对不要直接在生产库上重放 binlog。 正确做法是找一台空机器(或临时实例),在那里完成恢复,再把需要的数据导回来。
# 1. 在临时实例上恢复最近一次全量备份
gunzip < /data/backup/mysql/20261010/mydb_020000.sql.gz | mysql -uroot -p
# 2. 重放 binlog 到误操作前一刻
mysqlbinlog --start-datetime='2026-10-10 02:00:00' \
--stop-datetime='2026-10-10 09:34:59' \
/data/mysql-bin/mysql-bin.000122 /data/mysql-bin/mysql-bin.000123 \
| mysql -uroot -p
多个 binlog 文件必须一次性传给 mysqlbinlog,不要分成多次执行(会破坏事务边界)。文件顺序必须按编号排列。
也可以用 position 精确定位(比时间更准):
mysqlbinlog --start-position=4 --stop-position=12345678 \
/data/mysql-bin/mysql-bin.000123 | mysql -uroot -p
第四步:只导出误删的数据,再导回生产库
这是最重要的方法论。 不要直接把"恢复到 09:34"的实例当成生产库使用——那样会丢失 09:34 之后所有正常的业务写入。
正确做法:
# 1. 确认误删的数据范围
mysql -uroot -p -e "SELECT COUNT(*) FROM mydb.orders WHERE created_at > '2026-10-01'"
# 2. 只导出这部分
mysqldump -uroot -p --single-transaction \
--where="id BETWEEN 10000 AND 20000" \
mydb orders > /tmp/orders_recovered.sql
# 3. 在导回前,先检查生产库里这些行的现状(避免覆盖已有数据)
mysql -uroot -p -e "SELECT COUNT(*) FROM mydb.orders WHERE id BETWEEN 10000 AND 20000"
# 4. 导回(用 INSERT IGNORE 或先比对,避免主键冲突)
mysql -uroot -p mydb < /tmp/orders_recovered.sql
主键冲突的处理:如果误删的行 id 之后被新数据复用了,直接导入会冲突。这种情况要人工比对后再决定怎么合并——这也是为什么要第一时间停止写入。
第五步:验证与恢复服务
-- 数据行数是否合理
SELECT COUNT(*) FROM orders;
-- 关键业务查询是否正常
SELECT * FROM orders WHERE user_id = 12345 ORDER BY id DESC LIMIT 10;
-- 最近的数据是否还在
SELECT MAX(id), MAX(created_at) FROM orders;
确认无误后:
SET GLOBAL super_read_only = OFF;
SET GLOBAL read_only = OFF;
UNLOCK TABLES;
sudo systemctl start your-app
如果误操作是 DDL(DROP TABLE)
DROP TABLE 在 binlog 里也是一条语句,时间点恢复的方法同样适用。但要注意:DROP TABLE 之后如果又创建了同名表,重放 binlog 时会在建表语句上出错,这时候需要手工编辑 binlog 导出的 SQL,跳过冲突部分。
另一个更快的办法:如果开了 binlog 且是 ROW 格式,DROP TABLE 之前的所有行变更都在 binlog 里,可以从中"反向"生成恢复语句。社区有专门的工具(如 binlog2sql)能把 ROW 格式的 binlog 反向解析成回滚 SQL:
# 思路:解析出 DELETE 语句,反向生成 INSERT
python binlog2sql.py --flashback -h127.0.0.1 -uroot -p \
--start-file='mysql-bin.000123' --start-datetime='2026-10-10 09:30:00' \
--stop-datetime='2026-10-10 09:35:00' -d mydb -t orders > rollback.sql
这类工具要先用小范围数据验证正确性再执行,不要盲目信任生成的 SQL。
六、文件被删除了,没有备份怎么办
先说结论:成功率很低,不要抱太大希望,但可以尝试。
删除文件到底发生了什么
Linux 上删除文件(rm)的本质是:断开文件名到 inode 的链接(unlink),并把 inode 的链接数减一。 当链接数为 0 且没有进程打开它时,inode 被标记为可回收,它指向的数据块被加入空闲列表。
关键点:删除只是"解除引用",数据本身还留在磁盘上,直到那些块被新数据覆盖。 所以恢复的可能性取决于"那些块有没有被覆写"。
哪些情况几乎不可能恢复:
- 删除后系统继续大量写入(数据库还在跑、日志在写、备份在跑)——块很快被复用
- SSD 上的 TRIM:现代 SSD 在删除文件时会被触发 TRIM,控制器会主动擦除对应块,且磨损均衡和垃圾回收会打乱物理映射。SSD 上删除恢复的成功率远低于机械盘
- 日志文件系统的某些行为(虽然 ext4/xfs 都是日志文件系统,但日志记录的是元数据,不影响这个结论)
- 删除时间已经很久(几天以上)
ext4 上的恢复
ext4 在删除时会清空 inode 中的块指针(这是与 ext3 的重要区别),这让恢复变得更难——你无法直接从 inode 知道文件曾经占用了哪些块。
可用的工具:
# 安装 extundelete
# RHEL/CentOS/Rocky
sudo dnf install -y extundelete
# Debian/Ubuntu
sudo apt-get install -y extundelete
# 必须先卸载分区或挂载为只读(关键!)
sudo umount /dev/sdb1
# 或者
sudo mount -o remount,ro /dev/sdb1
# 尝试恢复
sudo extundelete /dev/sdb1 --restore-file /path/to/deleted/file
sudo extundelete /dev/sdb1 --restore-all
最重要的一步是卸载或只读挂载。 继续写入会覆盖你要恢复的数据。如果分区正在被使用无法卸载,至少要 mount -o remount,ro。
成功率:对于刚删除、且分区立即被设为只读的场景有一定希望;对于 ext4 上删除已久的文件,成功率很低。
xfs 上的恢复
XFS 的删除行为与 ext4 类似(也会清空相关元数据)。常用工具是 xfs_undelete(第三方工具,不是 XFS 官方自带)。
# xfs_undelete 需要单独获取,部分发行版仓库里没有
sudo xfs_undelete /dev/sdb1 -o /recovery/
关于 xfs_undelete 要如实说明几点:
- 它主要通过扫描文件系统里的目录项和 inode 特征来"猜测"已删除文件的内容
- 无法恢复文件名(恢复出来的文件按 inode 号或时间命名,需要你人工识别)
- 无法保证文件完整——文件如果被碎片化存储,或者部分块已被复用,恢复出来的内容可能是残缺的
- 成功率随时间推移快速下降
photorec 与 foremost
photorec(属于 testdisk 包)是另一种思路:它不依赖文件系统元数据,而是按文件特征(文件头、文件尾的魔数)扫描整个磁盘,把能识别出来的文件"雕刻"出来。
# RHEL/CentOS/Rocky
sudo dnf install -y testdisk
# Debian/Ubuntu
sudo apt-get install -y testdisk
sudo photorec /dev/sdb
# 交互式选择分区、文件系统类型、要恢复的文件类型、输出目录
photorec 的特点:
- 能恢复各种常见格式(图片、文档、压缩包、视频等)
- 完全丢失文件名和目录结构,输出是成千上万个按序号命名的文件,需要你自己筛
- 对碎片化的大文件几乎无能为力(它是按连续块拼接的)
- 输出量可能非常大,需要准备足够的空间
适用场景:误删了一批照片或文档、且磁盘之后写入不多。对数据库文件(通常是几个巨大的、高度碎片化的文件)基本无效。
正确的处置流程
1. 立即停止一切写入。 这一点怎么强调都不过分:
sudo systemctl stop mysqld nginx your-app
sudo mount -o remount,ro /data # 把分区改成只读
做不到卸载至少要做只读重挂载。 如果分区是根分区无法卸载,那就停掉所有会写盘的服务。每多写一秒,恢复的希望就少一分。
2. 先对整个盘做镜像,在镜像上操作。
sudo dd if=/dev/sdb of=/mnt/recovery/disk.img bs=4M status=progress
# 或者用 ddrescue(对有坏道的盘更友好)
sudo ddrescue -f -n /dev/sdb /mnt/recovery/disk.img /mnt/recovery/rescue.log
在镜像上恢复,永远不要在原盘上直接操作。 这样即使恢复尝试失败,原盘状态也保持不变,还能换别的工具再试。
3. 用工具扫描镜像。
sudo photorec /mnt/recovery/disk.img
4. 接受现实。 如果以上都失败了,那就只能接受数据丢失。这也是为什么备份如此重要——恢复工具是最后的、成功率很低的手段,不是方案。
数据库文件删除的特殊说明
如果误删的是 MySQL 的数据文件(ibd、frm)而 MySQL 进程还在运行,有一个特殊的机会:进程还持有已删除文件的文件描述符,可以通过 /proc 找回。
# 找到 mysqld 进程打开的已删除文件
sudo lsof -p $(pidof mysqld) | grep deleted
输出里的 /proc/ 就是那个已删除文件的句柄,可以把它复制出来:
sudo cp /proc/1234/fd/56 /recovery/orders.ibd
这个方法有严格前提:MySQL 进程必须还在运行、且没有关闭过这张表。 一旦进程重启或表被关闭,句柄就没了。
重要提醒:不要试图用复制出来的单个 ibd 文件直接在运行中的 MySQL 里"接回去",这通常会导致表空间损坏。 正确做法是整个数据目录一起恢复,或者在临时实例上做。
七、云盘快照回滚:回滚前必须验证
云盘快照回滚是最方便的恢复手段,但它有必须注意的地方。
回滚前的验证清单
1. 确认快照时间点确实包含你需要的数据。 快照是在某个时刻创建的,如果误操作发生在快照之后,回滚到那个快照会丢失之后的所有正常数据。
2. 回滚是不可逆的。 云盘回滚到快照后,快照之后的写入会被丢弃。回滚前先对当前云盘做一次快照,给自己留一条退路。
3. 数据库一致性问题。 这是最容易踩的坑:
- 直接对正在运行的数据库做云盘快照,快照里的数据文件可能处于"正在写入"的不一致状态(类似服务器突然断电)
- 恢复后 MySQL 会做崩溃恢复(crash recovery),通常能自愈,但不保证,尤其是
innodb_flush_log_at_trx_commit设成 2 或者sync_binlog不为 1 时
正确的做法是先做一致性快照:
-- 刷盘并短暂锁表后再触发快照
FLUSH TABLES WITH READ LOCK;
-- 在另一个会话触发云盘快照,完成后
UNLOCK TABLES;
或者用文件系统快照 / LVM 快照,配合 FLUSH TABLES WITH READ LOCK 让 InnoDB 处于一致点。
4. 验证回滚结果。 回滚后要检查:
# 看 MySQL 错误日志有没有崩溃恢复记录
sudo tail -100 /var/log/mysql/error.log
-- 检查数据完整性
CHECK TABLE 核心表;
SELECT COUNT(*) FROM 核心表;
数据盘与系统盘分离的价值
这是云上一个非常值得做的实践。
分离的做法:系统盘只装操作系统和应用代码,数据盘单独挂载(比如 /data)存放数据库文件、用户上传文件、日志。
这样做的好处:
| 好处 | 说明 |
|---|---|
| 可以只回滚数据盘 | 误操作后回滚数据盘,系统和应用的配置、代码不受影响 |
| 系统盘故障不影响数据 | 系统崩溃、需要重装系统时,数据盘直接挂上就能恢复 |
| 可以换系统不换数据 | 升级操作系统、换镜像时,数据盘可以原样挂载 |
| 备份策略可以分开 | 系统盘镜像 + 数据盘快照,各自按不同频率 |
| 扩容更灵活 | 数据盘可以在线扩容,不影响系统盘 |
| 故障隔离 | 系统盘写满不会导致数据库崩溃(反过来也一样) |
反面例子:所有东西都在系统盘上,数据库写满了系统盘,导致连 SSH 都登不进去,这时候连排查都做不了,只能靠控制台进单用户模式或者回滚整个系统盘(连应用配置一起回滚)。
数据盘挂载的建议
# 用 UUID 挂载,不要用设备名
sudo blkid /dev/vdb
# /etc/fstab
UUID=xxxx-xxxx /data xfs defaults,noatime 0 0
系统盘和数据盘用不同的盘,并且数据盘的监控告警要单独配置(使用率超过 80% 就告警)。
八、制定 DR 预案与定期演练
预案要写成文档,并且具体到可执行
一份可用的 DR 预案至少要包含:
1. 联系人与决策链:谁发现问题、谁有权决定切换、各人的联系方式(要有备用联系方式)。
2. 系统清单与优先级:哪些系统必须先恢复(核心交易)、哪些可以后恢复(报表、后台)。不要试图在灾难中恢复所有东西。
3. 每一步的具体命令。 预案里写"恢复数据库"是不够的,要写到:
# 1. 在备用机启动实例
sudo systemctl start mysqld
# 2. 导入最新全量备份
gunzip < /data/backup/mysql/20261010/all.sql.gz | mysql -uroot -p
# 3. 重放 binlog
mysqlbinlog ... | mysql -uroot -p
# 4. 验证
mysql -uroot -p -e "SELECT COUNT(*) FROM mydb.orders"
4. 切换方式:DNS 改解析(注意 TTL)、负载均衡切后端、还是改应用配置重启。
5. 验证方法:怎么确认业务真的恢复了(健康检查 URL、关键业务查询、数据行数对比)。
6. 回滚到原状的流程:主站修好后怎么切回去。
演练的重要性
没有演练过的预案等于没有预案。 演练能发现的问题包括:备份文件其实是坏的、恢复脚本有 bug、备用机资源不够、文档里的命令已经过时、依赖的外部服务没有配置。
演练的频率建议:
- 恢复演练:每季度一次(把备份恢复到测试环境并验证)
- 完整 DR 演练:每年一到两次(模拟主站不可用,完整走一遍切换流程)
- 关键人员变动后:重跑一次
演练要记录实测数据:实际恢复耗时(真实的 RTO)、实际丢失的数据量(真实的 RPO)、遇到的问题清单。这些数字才是有意义的,纸面上的 RTO 目标没有价值。
监控与告警
备份失败的告警必须是最优先级的。 一个静默失败的备份比没有备份更危险——因为它给了你虚假的安全感。
要监控的:
- 备份任务执行结果(成功/失败)与耗时趋势
- 备份文件大小(突然变小说明可能不完整)
- binlog 同步是否持续
- 备份存储剩余空间
- 主从复制延迟与状态
- 磁盘使用率(超过 80% 告警)
# 一个简单的备份检查:文件是否存在且是今天生成的
find /data/backup/mysql -name '*.sql.gz' -mtime -1 | wc -l
把这个检查纳入监控系统,为零时告警。
常见问题
备份做了但从来没验证过,现在需要恢复,来得及吗?
先做一件事:立刻对当前状态再做一份备份(哪怕数据已经坏了),然后再尝试恢复。这样至少有退路。之后的事只能看运气——这也是为什么强调恢复演练。
快照可以当备份用吗?
不可以,或者说不完整。快照与源数据在同一存储系统上,防不了存储系统故障、机房事故、账号被删。快照适合做快速回滚(分钟级恢复误操作),但真正的灾难恢复需要独立、异地的备份。 两者都要有。
rm 删了文件还有救吗?
取决于:是不是机械盘(SSD 因 TRIM 成功率大幅降低)、删除后有没有继续写入(立即设为只读才有希望)、删除了多久(越久越难)。第一步永远是停止写入并只读挂载,第二步是先做整盘镜像再在镜像上尝试恢复。 但请如实评估:成功率整体不高,数据库这类大文件尤其低。
误删数据后第一件事应该做什么?
停止写入。 不是查日志、不是找工具、不是重启服务——是立刻阻止任何新的写入(锁表、设只读、或停应用),并对当前状态做一次备份。这一步决定了后续所有操作有没有意义。
延迟从库能防误操作吗?
能,而且很有性价比。设 MASTER_DELAY = 3600 让从库延迟一小时执行主库的变更,主库上误删后,你有一小时的时间窗口去从库上把数据捞出来。代价是从库的数据是旧的,不能用来做读写分离,且占用一份完整的资源。
恢复演练要在生产环境做吗?
不要。恢复演练在独立的测试环境做:把备份文件恢复到一个临时实例,验证数据完整性,记录耗时。演练本身不应该有破坏生产的风险。生产环境上做的是"切换演练"(需要提前公告并做好准备),这和恢复演练是两回事。
云盘快照回滚会丢数据吗?
会。回滚到某个快照点,快照之后的所有写入都会被丢弃。这就是为什么要先确认快照时间点、并在回滚前对当前状态再做一次快照。 数据库场景还要特别注意一致性——运行中直接打快照可能拿到不一致的数据文件。
两地三中心是不是太重了?
对绝大多数企业来说是。更实际的是:生产环境 + 异地备份(全量 + binlog 同步到另一个地域的对象存储),RPO 分钟级、RTO 小时级、成本很低。只有金融、医疗这类有强合规要求的行业才需要考虑完整的多中心方案。
总结
- 恢复的成败在事故前就决定了:先定 RPO 和 RTO(问业务方"最多能丢多久数据、能停多久服务"),再倒推方案。RPO 越小、RTO 越短,成本和复杂度呈阶跃式上升,不要盲目追求 RPO = 0。
- 备份策略用"全量 + 增量 + binlog":每周全量 + 每天增量 + 持续 binlog,配合 3-2-1 原则(3 份副本、2 种介质、1 份异地)。binlog 用
mysqlbinlog --stop-never持续拉取,或设一个延迟从库(MASTER_DELAY)专门用来防误操作。 - 快照不等于备份:快照与源数据在同一存储系统,防不了存储故障、机房事故、账号被删,它是"分钟级快速回滚"的工具。正确组合是"快照保留 7 天做回滚 + 备份同步异地保留 30 天做灾难恢复"。
- 误删数据的标准流程:停止写入(锁表/
super_read_only/停应用)→ 对当前状态再备份一次保护现场 → 用mysqlbinlog -v定位误操作时刻 → 在独立实例上恢复全量并重放 binlog → 只导出误删的那部分数据再导回生产,而不是直接把恢复实例当生产库用,这样才不会丢失误操作之后的正常写入。 - 文件删除恢复成功率很低,要如实看待:ext4 和 XFS 删除时会清空块指针信息,SSD 还会因 TRIM 主动擦除;
extundelete、xfs_undelete对刚删除且立即只读的场景有一定希望;photorec靠文件魔数雕刻,会丢失文件名和目录结构、对碎片化大文件基本无效。第一步永远是停止写入 + 只读挂载 + 先做整盘镜像再在镜像上操作。 - 数据库文件被删但进程还在时有一线机会:用
lsof -p找到| grep deleted /proc/把文件复制出来,但不要把单个 ibd 直接接回运行中的实例。/fd/N - 云盘回滚前必须先给当前状态做快照,并确认快照时间点的数据与一致性(运行中打快照要先
FLUSH TABLES WITH READ LOCK)。数据盘与系统盘分离能让你只回滚数据、系统重装不影响数据、且避免系统盘被写满导致连 SSH 都登不进去。 - 预案要具体到命令,并定期演练:每季度做一次恢复演练(在测试环境),每年做一到两次完整切换演练,记录真实的 RTO/RPO 数字和遇到的问题。备份失败的告警要给最高优先级——静默失败的备份比没有备份更危险,因为它给了虚假的安全感。
企业QQ咨询




