外观
案例:备份与恢复演练
现象
误删了一张表,需要从备份恢复。运维说"每天都有备份",但真正打开备份目录时发现:
bash
ls -lh /data/backup/ | tail -5text
-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看起来没问题。但:
- 备份只包含
shop库,误删的是shop_archive库。 - 备份文件全在同一块盘上。
- 从没做过恢复演练,不知道要多久。
定位过程
第一步:确认备份内容
bash
zcat /data/backup/shop-2026-10-28.sql.gz | grep -E '^-- Current Database' | headtext
-- 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 -3text
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/datatext
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 28m14s2.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_restoreshop_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 |
备份的三个真相
- 没演练过的备份不算备份。
- 同盘的备份不算备份。
- 没有校验的备份可能是空文件。
三条都满足,才敢说"我们有备份"。
RPO 与 RTO
- RPO(能丢多少数据):由备份频率 + binlog 决定。每天全备 + binlog = 最多丢几秒。
- RTO(多久能恢复):由备份类型 + 演练决定。逻辑备份 2GB 要 28 分钟,物理备份可能 3 分钟。
先和业务确认这两个数字,再设计备份方案。