数据库备份
如何定时执行 MySQL 和 PostgreSQL 转储、将其移出机器,并验证备份确实可以恢复。
更新于 2026年8月23日
VPS 不提供自动的机器备份或状态快照:任何您没有自行复制的内容都只存在于单一实例中。数据库转储是您首先应该设置的东西,因为数据没有其他地方可以恢复,而网站可以从头重新部署。
手动转储
PostgreSQL:
pg_dump -Fc -U shop -h 127.0.0.1 shop > /srv/backups/shop-$(date +%F).dump
pg_dumpall -U postgres --roles-only > /srv/backups/roles.sql
MySQL 和 MariaDB:
mysqldump --single-transaction --quick --routines --events \
-u root shop | gzip > /srv/backups/shop-$(date +%F).sql.gz
--single-transaction 可以在不锁定表的情况下获取一致的 InnoDB 转储——没有它,您的网站会在导出期间卡住。PostgreSQL 的 -Fc 格式经过压缩,并允许您恢复单个表。
不要把密码写在命令行上:创建权限为 600 的 ~/.pgpass 或 ~/.my.cnf。
定时执行
mkdir -p /srv/backups && chmod 700 /srv/backups
crontab -e
20 3 * * * /usr/local/bin/db-backup.sh >> /var/log/db-backup.log 2>&1
/usr/local/bin/db-backup.sh 脚本:
#!/bin/bash
set -euo pipefail
DEST=/srv/backups
STAMP=$(date +%F-%H%M)
pg_dump -Fc -U shop -h 127.0.0.1 shop > "$DEST/shop-$STAMP.dump.tmp"
mv "$DEST/shop-$STAMP.dump.tmp" "$DEST/shop-$STAMP.dump"
find "$DEST" -name '*.dump' -mtime +14 -delete
先写入临时文件再重命名很重要:如果转储被中断,您不会把截断的文件误认为是完整的备份。set -euo pipefail 会在第一个错误处停止脚本,而不是静默地继续执行。
将备份移出机器
同一块磁盘上的副本无法让您免于迁移失误、勒索软件,或在“Access”标签页重装操作系统。请将转储发送到异地:
rsync -az --delete /srv/backups/ backup@other-host:/store/vds/
或者通过 rclone 发送到任何兼容 S3 的存储。请记住最高 500 Mbit/s 的带宽以及每档的磁盘写入限制:每核心 60 MB/s。
恢复检查
从未恢复过的备份不算备份。
createdb -U postgres shop_check
pg_restore -U postgres -d shop_check /srv/backups/shop-2026-08-23.dump
psql -U postgres -d shop_check -c "SELECT count(*) FROM users;"
dropdb -U postgres shop_check
对于 MySQL:gunzip -c dump.sql.gz | mysql -u root shop_check。
每月执行一次,并记录检查日期。
常见错误
| 现象 | 原因 |
|---|---|
| 转储只有几 KB | 脚本在身份验证时失败,错误被输出到了 /dev/null |
| 磁盘空间耗尽 | 没有按 mtime 删除旧备份 |
| 恢复时因权限失败 | 没有转储角色——您需要 --roles-only |
| Cron 静默运行,没有备份 | Cron 没有您的 PATH——请使用绝对路径 |
| 网站在夜间变慢 | 转储争抢磁盘——请调整时间,添加 --quick |
请在不同位置保留至少两份副本,并监控转储大小:大小骤降意味着并非整个数据库都被转储了。