本文约 3,709 字,预计阅读 10 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:未经验证的备份,等于没有备份
备份失败的几种典型形态:
- 备份文件是 0 字节(脚本密码写错);
- 备份是三个月前的(cron 没跑起来);
- 备份缺了 wp-content(排除规则写错);
- 备份能导入,但字符集不对,中文全乱码。
这些只有在恢复时才会暴露。所以恢复演练不是”可选”,而是备份流程的一部分。
原理分析:验证要覆盖四个层面
| 层面 | 验证什么 | 方法 |
|---|---|---|
| 完整性 | 文件是否完整、未损坏 | 大小校验、gzip -t、校验和 |
| 可导入性 | SQL 能否成功执行 | 导入测试库 |
| 内容正确性 | 数据是否完整、字符集是否正常 | 抽查记录数、中文与 emoji |
| 站点可用性 | 恢复后站点能否正常访问 | 启动站点、访问前台后台 |
只做第一步是不够的——文件完整但导入失败的情况很常见(字符集、排序规则、语法差异)。
保姆级实操
第一步:文件完整性校验
# 大小检查
ls -lh /backup/db/*.sql.gz | tail -5
# gzip 完整性
gzip -t /backup/db/db-2026-10-10.sql.gz && echo "OK"
# 校验和比对(备份时生成,恢复时验证)
sha256sum /backup/db/db-2026-10-10.sql.gz
生成校验和的备份脚本片段:
sha256sum "$FILE" >> "${BK}/checksums.txt"
第二步:导入到测试库
mysql -e "DROP DATABASE IF EXISTS wp_restore_test;"
mysql -e "CREATE DATABASE wp_restore_test CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
gunzip < /backup/db/db-2026-10-10.sql.gz | mysql wp_restore_test
echo "导入退出码: $?"
第三步:内容抽查
mysql wp_restore_test -e "
SELECT COUNT(*) AS 文章数 FROM wp_posts WHERE post_status='publish';
SELECT COUNT(*) AS 用户数 FROM wp_users;
SELECT option_value FROM wp_options WHERE option_name='home';
SELECT post_title FROM wp_posts WHERE post_status='publish' LIMIT 3;
"
重点关注:
- 文章数与生产库是否接近;
home选项的值是否正确;- 中文标题是否正常显示(不是
???或乱码); - 是否有 emoji 能正常读出。
第四步:恢复站点文件并启动
在一台测试机(或本机另一个目录)完整还原:
mkdir -p /tmp/restore-test
tar xzf /backup/files/files-2026-10-10.tar.gz -C /tmp/restore-test
创建测试库、修改 wp-config.php 指向测试库,配一个临时 Nginx server 块(用不同端口或测试域名),然后访问:
curl -I http://127.0.0.1:8080/
检查:
- 首页返回 200;
- CSS/JS 资源能加载(不是 404);
- 后台能登录;
- 媒体库图片能显示。
第五步:用 WP-CLI 验证
cd /tmp/restore-test
wp --path=/tmp/restore-test core verify-checksums
wp --path=/tmp/restore-test option get home
wp --path=/tmp/restore-test plugin list --status=active
wp --path=/tmp/restore-test db check
第六步:记录恢复时间(RTO)
用 time 记录整个过程耗时:
time (gunzip < db.sql.gz | mysql wp_restore_test)
这是你真实的 RTO 数据。如果恢复要 40 分钟而业务只能容忍 10 分钟,说明备份方案需要调整(比如改用快照或增量备份)。
第七步:把演练脚本化
#!/usr/bin/env bash
# /usr/local/bin/verify-backup.sh
set -uo pipefail
DB_BACKUP="${1:?用法: $0 <备份.sql.gz>}"
TEST_DB="wp_restore_test"
LOG="/var/log/backup-verify.log"
exec >> "$LOG" 2>&1
echo "=== $(date) 开始验证 $DB_BACKUP ==="
# 1. 完整性
gzip -t "$DB_BACKUP" || { echo "FAIL: 文件损坏"; exit 1; }
# 2. 导入
mysql -e "DROP DATABASE IF EXISTS $TEST_DB; CREATE DATABASE $TEST_DB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
gunzip < "$DB_BACKUP" | mysql $TEST_DB || { echo "FAIL: 导入失败"; exit 1; }
# 3. 抽查
POSTS=$(mysql -N -e "SELECT COUNT(*) FROM $TEST_DB.wp_posts WHERE post_status='publish';")
HOME=$(mysql -N -e "SELECT option_value FROM $TEST_DB.wp_options WHERE option_name='home';")
echo "文章数: $POSTS"
echo "站点地址: $HOME"
if [ "$POSTS" -lt 1 ]; then
echo "FAIL: 文章数为 0"; exit 1
fi
# 4. 清理
mysql -e "DROP DATABASE $TEST_DB;"
echo "PASS: 备份可用"
每月定时执行(选最近一份备份):
0 5 1 * * /usr/local/bin/verify-backup.sh $(ls -t /backup/db/*.sql.gz | head -1)
第八步:演练检查清单(每月打勾)
- [ ] 最近一次备份文件存在且非空
- [ ] gzip 完整性通过
- [ ] 能成功导入测试库
- [ ] 文章数与生产库一致(±5%)
- [ ] 中文与 emoji 正常
- [ ] 站点能启动、后台能登录
- [ ] 媒体库图片正常
- [ ] 记录本次 RTO
- [ ] 异地备份同样抽检一次
常见踩坑与失败案例
- 演练也在生产库上做 —— 千万别。
DROP DATABASE写错对象就是灾难。永远用独立的测试库,且脚本里写死库名。 - 案例:某公司每季度演练,但一直只做”文件完整性校验”,从未真的导入。真正出事时发现 SQL 里有 MariaDB 不认识的排序规则,导入失败。教训是必须真的导入一次。
- 测试库与生产库字符集不同 —— 导入成功但中文乱码,误判为备份损坏。创建测试库时要显式指定
utf8mb4。 - 演练完忘记清理测试库 —— 占用磁盘。脚本最后加
DROP DATABASE。 - 只在本地验证,异地备份从未验证 —— 异地备份同样要抽检,至少每季度下载一份验证。
总结
恢复演练的核心动作就四个:校验完整性 → 导入测试库 → 抽查内容 → 启动验证。前两个能发现 90% 的问题,后两个能发现剩下 10%(字符集、路径、权限)。把它做成脚本每月自动跑一次,结果写进运维日志,你会发现很多”以为没问题”的备份其实早就有问题。备份这件事,验证过的才算数。
