SQLite凭借零配置、单文件、易嵌入的特性,被大量应用在小型站点、嵌入式设备、桌面软件和中小型项目中。正因为整个数据库就是一个文件,很多人图省事直接用cp命令复制一份就算备份了。这种做法在数据库没有写入时没问题,可一旦备份发生在事务执行过程中,复制出来的文件就可能包含半新半旧的数据页,恢复时轻则丢数据,重则整个文件损坏无法打开。本文就来聊聊如何用cron配合正确的备份姿势,搭建一套可靠的SQLite定时备份机制。

为什么不能直接复制数据库文件
SQLite在写入时依赖日志文件( rollback journal 或 WAL 文件)来保证原子性。当你执行cp db.sqlite db_backup.sqlite时,操作系统并不保证主数据库文件和日志文件被同时一致地复制。假设一个事务写了一半,主文件里的部分数据页已经更新,而对应的回滚信息还在journal文件里,此时复制出来的副本既不是事务前状态也不是事务后状态,就是一份损坏的数据。
即使开启了WAL模式,情况也没有好转多少。WAL模式下写入先进入-wal文件,checkpoint时机不确定,直接复制主文件很可能拿到的是一份缺少最新写入的旧数据。所以在数据库可能被写入的任何时刻,裸复制文件都不是安全的备份手段,除非你确定写入已经完全停止。
正确的做法是让SQLite自己来完成快照。sqlite3命令行工具提供了.backup命令,它内部调用在线备份API(Online Backup API),在读取源库时会持有读锁,保证读到的内容是一份完整、一致的数据快照,同时对线上业务的写入阻塞也降到了最低。
编写一个可用的备份脚本
先确认机器上安装了sqlite3工具,大部分Linux发行版可以直接通过包管理器安装。备份命令本身非常简单:
sqlite3 /data/app/mydb.sqlite ".backup '/data/backup/mydb.sqlite'"
这条命令会生成一份完整的、可直接使用的备份文件。不过生产环境的备份脚本还需要考虑文件名带上时间戳、目录自动创建、错误告警等问题,下面是一个比较完整的示例:
#!/bin/bash
# SQLite 定时备份脚本
DB_PATH="/data/app/mydb.sqlite"
BACKUP_DIR="/data/backup/sqlite"
KEEP_DAYS=7
DATE=$(date +%Y%m%d_%H%M%S)
LOG_FILE="/var/log/sqlite_backup.log"
mkdir -p "$BACKUP_DIR"
# 执行在线备份,压缩节省空间
if sqlite3 "$DB_PATH" ".backup '$BACKUP_DIR/mydb_$DATE.sqlite'"; then
gzip "$BACKUP_DIR/mydb_$DATE.sqlite"
echo "[$(date '+%F %T')] 备份成功: mydb_$DATE.sqlite.gz" >> "$LOG_FILE"
else
echo "[$(date '+%F %T')] 备份失败!" >> "$LOG_FILE"
exit 1
fi
# 清理超过保留期的旧备份
find "$BACKUP_DIR" -name "mydb_*.sqlite.gz" -mtime +$KEEP_DAYS -delete
脚本里做了三件事:调用.backup生成一致性快照、用gzip压缩减少磁盘占用、用find -mtime清理超过7天的旧文件。建议先手动执行一次脚本,确认备份文件可以正常打开:用gzip -d解压后执行sqlite3 备份文件 "PRAGMA integrity_check;",返回ok才说明这份备份真的可用。备份没有验证过,等于没备份。
配置cron定时任务
脚本写好后,把它交给cron调度。编辑当前用户的crontab:
crontab -e
加入一行任务,例如每天凌晨两点半执行:
30 2 * * * /opt/scripts/backup_sqlite.sh >> /var/log/sqlite_backup.log 2>&1
cron表达式五个字段依次是分钟、小时、日、月、星期。如果想每6小时备份一次,可以写成0 */6 * * *;每周日凌晨备份则是0 3 * * 0。有几个细节容易踩坑:cron执行环境没有你的用户环境变量,脚本里涉及到的路径都要写绝对路径;sqlite3如果不是装在标准路径下,要么在脚本里写全路径,要么手动 export PATH;crontab中的百分号%有特殊含义,如果命令里要用到,必须转义成\%,这也是不少人在日志时间戳上翻车的常见原因。
任务配置完后可以用grep CRON /var/log/syslog或查看系统日志确认任务确实被触发。对于重要程度更高的库,建议把同步到异地也纳入脚本,比如用rsync把备份目录推到另一台机器,或者上传到对象存储,避免服务器磁盘整体故障时备份跟着一起丢失。
备份频率与恢复演练建议
备份频率取决于数据的重要性和写入量。纯读多写少的配置库,每天一次足够;如果有频繁的用户写入,比如订单或日志类数据,可以缩短到每小时甚至更短,同时通过控制KEEP_DAYS和备份频率来平衡磁盘占用。WAL模式下库比较大时,.backup会读取所有页生成完整副本,频率过高会带来一定IO压力,需要结合服务器负载观察调整。
最后强调一点:备份的最终目的是恢复。建议每隔一段时间做一次恢复演练,把备份文件解压到测试环境,跑一遍integrity_check和应用的核心功能,确认数据完整、业务能正常起来。只有被验证过能恢复的备份,才算是真正为数据安全兜了底。