Skip to content

案例:备份与恢复演练 ​

现象 ​

误删了一张表,需要从备份恢复。运维说"每天都有备份",但真正打开备份目录时发现:

bash
ls -lh /data/backup/ | tail -5
text
-rw-r--r-- 1 root root 2.1G Oct 28 03:00 shop-2026-10-28.sql.gz
-rw-r--r-- 1 root root 2.1G Oct 27 03:00 shop-2026-10-27.sql.gz
-rw-r--r-- 1 root root 2.1G Oct 26 03:00 shop-2026-10-26.sql.gz

看起来没问题。但:

  1. 备份只包含 shop 库,误删的是 shop_archive 库。
  2. 备份文件全在同一块盘上。
  3. 从没做过恢复演练,不知道要多久。

定位过程 ​

第一步:确认备份内容 ​

bash
zcat /data/backup/shop-2026-10-28.sql.gz | grep -E '^-- Current Database' | head
text
-- Current Database: `shop`

只有 shop。备份脚本:

bash
mysqldump -uroot -p"$PASS" shop | gzip > /data/backup/shop-$(date +%F).sql.gz

写死了库名。

第二步:检查备份完整性 ​

bash
gzip -t /data/backup/shop-2026-10-28.sql.gz && echo OK
zcat /data/backup/shop-2026-10-28.sql.gz | tail -3
text
OK
-- Dump completed on 2026-10-28  3:12:44

这一份是完整的。但如果服务器磁盘满了,脚本可能生成截断文件而不报错。

bash
for f in /data/backup/*.sql.gz; do
  gzip -t "$f" 2>/dev/null || echo "BROKEN: $f"
done

第三步:确认备份和数据库是否同盘 ​

bash
df -h /data/backup /data/mysql/data
text
Filesystem      Size  Used Avail Use% Mounted on
/dev/vdb        500G  412G   63G  87% /data
/dev/vdb        500G  412G   63G  87% /data

同一个 /data。盘坏、误删、勒索软件,数据和备份一起没。

第四步:估算恢复时间 ​

在测试机上实际演练:

bash
time (gunzip -c shop-2026-10-28.sql.gz | mysql -uroot -p"$PASS" shop_restore)
text
real	28m14s

2.1GB 的 dump 恢复要 28 分钟。这期间业务完全不可用——RTO 远超预期。

处置 ​

立即:从现有备份恢复能救的部分 ​

bash
mysql -uroot -p -e 'CREATE DATABASE shop_restore;'
gunzip -c /data/backup/shop-2026-10-28.sql.gz | mysql -uroot -p shop_restore

shop_archive 无法恢复,只能从 binlog 重放:

bash
mysqlbinlog --start-datetime='2026-10-28 03:00:00' \
            --stop-datetime='2026-10-28 09:14:00' \
  /data/mysql/logs/binlog.000042 | mysql -uroot -p

前提:binlog 没被清理,且 binlog_format=ROW。

根治:重建备份体系 ​

bash
#!/usr/bin/env bash
# /opt/backup/mysql-backup.sh
set -euo pipefail
exec >> /var/log/mysql-backup.log 2>&1

echo "[$(date '+%F %T')] start"

PASS="${DB_PASS:?}"
DST="/data/backup/mysql/$(date +%F)"
mkdir -p "$DST"

# 1. 全量逻辑备份(所有库)
mysqldump -uroot -p"$PASS" \
  --single-transaction --quick --routines --triggers --events \
  --all-databases --set-gtid-purged=OFF \
  | gzip > "$DST/all.sql.gz"

# 2. 校验
gzip -t "$DST/all.sql.gz"
SIZE=$(stat -c %s "$DST/all.sql.gz")
[[ "$SIZE" -gt 10485760 ]] || { echo "ERROR: too small"; exit 1; }

# 3. 记录 binlog 位置(用于时间点恢复)
mysql -uroot -p"$PASS" -e 'SHOW MASTER STATUS\G' > "$DST/master-status.txt"

# 4. 异地同步
rsync -az --delete "$DST/" backup@10.0.0.99:/backup/mysql/$(date +%F)/

# 5. 保留 14 天(本地) + 30 天(异地)
find /data/backup/mysql -maxdepth 1 -type d -mtime +14 -exec rm -rf {} +

echo "[$(date '+%F %T')] done size=$SIZE"

恢复演练(每季度) ​

bash
#!/usr/bin/env bash
# 在独立实例上演练,不碰生产
set -euo pipefail

RESTORE_DB="shop_drill_$(date +%Y%m%d)"
LATEST=$(ls -1t /data/backup/mysql/*/all.sql.gz | head -1)

mysql -h 10.0.0.88 -uroot -p"$DRILL_PASS" -e "DROP DATABASE IF EXISTS $RESTORE_DB; CREATE DATABASE $RESTORE_DB;"

time gunzip -c "$LATEST" | mysql -h 10.0.0.88 -uroot -p"$DRILL_PASS" "$RESTORE_DB"

# 数据校验:对比行数
mysql -h 10.0.0.88 -uroot -p"$DRILL_PASS" -e "
  SELECT 'orders' t, COUNT(*) c FROM $RESTORE_DB.orders
  UNION ALL SELECT 'users', COUNT(*) FROM $RESTORE_DB.users;"
text
+--------+---------+
| t      | c       |
+--------+---------+
| orders | 1842033 |
| users  |  128441 |
+--------+---------+

与生产对比:

bash
mysql -uroot -p -e 'SELECT COUNT(*) FROM shop.orders;'
text
1842033

一致才算演练通过。

复盘 ​

问题根因改进
只能恢复部分库备份写死了 shop--all-databases
备份与数据同盘没规划异地 rsync / 对象存储
RTO 28 分钟只有逻辑备份加 xtrabackup 物理备份
从没演练觉得"有备份就行"每季度演练并记录耗时
不知道 binlog 位置没记录备份时保存 SHOW MASTER STATUS

备份的三个真相

  1. 没演练过的备份不算备份。
  2. 同盘的备份不算备份。
  3. 没有校验的备份可能是空文件。

三条都满足,才敢说"我们有备份"。

RPO 与 RTO

  • RPO(能丢多少数据):由备份频率 + binlog 决定。每天全备 + binlog = 最多丢几秒。
  • RTO(多久能恢复):由备份类型 + 演练决定。逻辑备份 2GB 要 28 分钟,物理备份可能 3 分钟。

先和业务确认这两个数字,再设计备份方案。