SQLite 备份不能只复制 db 文件:做一次能恢复的演练

SQLite 很适合小型站点,但备份时不能只想着“把 .db 拷走”。如果数据库开了 WAL,现场可能还有 -wal-shm 文件;如果站点有上传目录,文章附件和头像也要一起保存;如果恢复顺序没写,真出事时很容易漏步骤。

下面按一个 Node/Express 小站点举例,路径可以按你的项目改。

先确认要备份什么

常见运行期文件:

data/blog.db
uploads/
.env

如果 SQLite 开了 WAL,运行中还可能看到:

data/blog.db-wal
data/blog.db-shm

不要手动拼这些文件。优先用 SQLite 自己的 .backup,它会拿到一致的数据库快照。

1. 建备份目录

mkdir -p data/backups

给备份文件带时间戳:

stamp=$(date +%Y%m%d-%H%M%S)
echo "$stamp"

macOS/Linux 可以直接用。Windows PowerShell 等价写法:

$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
$stamp

2. 备份数据库

Linux/macOS:

sqlite3 data/blog.db ".backup 'data/backups/blog-$stamp.sqlite'"

PowerShell:

sqlite3 data/blog.db ".backup 'data/backups/blog-$stamp.sqlite'"

验证备份文件:

sqlite3 data/backups/blog-$stamp.sqlite 'pragma integrity_check;'
sqlite3 data/backups/blog-$stamp.sqlite '.tables'

正常应该输出:

ok

只看到文件存在不算验收。至少要跑一次 integrity_check

3. 备份上传目录

Linux/macOS:

tar -czf data/backups/uploads-$stamp.tar.gz uploads

PowerShell 如果有 tar:

tar -czf data/backups/uploads-$stamp.tar.gz uploads

验证压缩包能列出内容:

tar -tzf data/backups/uploads-$stamp.tar.gz | head

如果项目暂时没有 uploads/,也要在备份说明里写清楚“没有上传目录”,不要让下一个维护者猜。

4. 记录环境变量模板,不要乱传密钥

.env 里经常有密钥,不能随便上传到 Git 或聊天工具。

保存两份东西:

  • .env.example:不含真实密钥,记录需要哪些变量。
  • 真实 .env:只进受控备份,不进 Git。

检查 Git 是否会误收:

git check-ignore -v .env || echo '.env is not ignored'

如果输出 .env is not ignored,修 .gitignore。不要等提交前再靠肉眼检查。

5. 写一份恢复顺序

放到 data/backups/RESTORE.md 或运维文档里都可以。内容不要写成概念,要写命令。

示例:

# 恢复顺序

1. 停止服务:`pm2 stop codenest`
2. 备份现有现场:`cp data/blog.db data/blog.db.before-restore`
3. 恢复数据库:`cp data/backups/blog-YYYYMMDD-HHMMSS.sqlite data/blog.db`
4. 恢复上传目录:`tar -xzf data/backups/uploads-YYYYMMDD-HHMMSS.tar.gz`
5. 检查数据库:`sqlite3 data/blog.db 'pragma integrity_check;'`
6. 启动服务:`pm2 start codenest`
7. 健康检查:`curl -fsS http://127.0.0.1:3000/health`
8. 查看日志:`tail -n 50 data/logs/error.log`

恢复文档的标准是:不熟悉项目的人照着做,也不会把顺序弄反。

6. 做一次临时目录恢复演练

不要第一次恢复就直接覆盖生产目录。先在临时目录演练。

mkdir -p /tmp/codenest-restore-test
cp data/backups/blog-$stamp.sqlite /tmp/codenest-restore-test/blog.db
tar -xzf data/backups/uploads-$stamp.tar.gz -C /tmp/codenest-restore-test
sqlite3 /tmp/codenest-restore-test/blog.db 'pragma integrity_check;'
find /tmp/codenest-restore-test -maxdepth 2 -type f | head

看两件事:

  • 数据库能打开且校验通过。
  • 上传文件确实解出来了。

7. 恢复到原项目时的安全顺序

先停服务:

pm2 stop codenest

如果不是 PM2,换成你的进程管理方式。关键是恢复数据库时不要还有写入。

保留现有现场:

cp data/blog.db data/blog.db.before-restore-$stamp

恢复数据库:

cp data/backups/blog-$stamp.sqlite data/blog.db

恢复上传目录前先挪走旧目录,不要直接混合覆盖:

mv uploads uploads.before-restore-$stamp
mkdir uploads
tar -xzf data/backups/uploads-$stamp.tar.gz

启动并验证:

pm2 start codenest
curl -fsS http://127.0.0.1:3000/health
tail -n 50 data/logs/app.log
tail -n 50 data/logs/error.log

如果健康检查失败,先看 error.log,不要连续重启赌运气。

8. 容易漏的东西

定时任务状态

如果项目有定时发布、队列、后台任务,数据库恢复到旧时间点后,任务可能会重复执行或漏执行。恢复文档里要写清楚恢复后检查哪张表、哪个后台页面或哪个日志事件。

外部回调

支付、OAuth、Webhook 这类外部系统通常不会因为你恢复数据库就自动回到旧状态。恢复后要检查回调积压和失败重试。

文件权限

恢复后的文件属主不对,服务也会启动失败:

ls -la data uploads

如果线上服务用单独用户运行,恢复后要确认服务用户能读写运行期目录。

9. 每周备份的最低验收

stamp=$(date +%Y%m%d-%H%M%S)
sqlite3 data/blog.db ".backup 'data/backups/blog-$stamp.sqlite'"
sqlite3 data/backups/blog-$stamp.sqlite 'pragma integrity_check;'
tar -czf data/backups/uploads-$stamp.tar.gz uploads
tar -tzf data/backups/uploads-$stamp.tar.gz >/dev/null
ls -lh data/backups/blog-$stamp.sqlite data/backups/uploads-$stamp.tar.gz

这些命令都过了,才算“这次备份能用”。