SQLite 完全指南 / 13 - 备份恢复
13 - 备份恢复:.backup、VACUUM 与在线备份 API
13.1 备份策略概览
| 方法 | 说明 | 是否在线 | 适用场景 |
|---|---|---|---|
| 文件复制 | 直接复制 .db 文件 | ❌ 需停止写入 | 简单备份 |
| .backup 命令 | CLI 备份命令 | ✅ 在线 | CLI 快速备份 |
| SQL 导出 | .dump 导出为 SQL | ✅ 在线 | 跨版本迁移 |
| VACUUM INTO | 清理并导出到新文件 | ✅ 在线 | 压缩备份 |
| 在线备份 API | C API 逐步备份 | ✅ 在线 | 应用程序集成 |
| 文件系统快照 | LVM/ZFS 快照 | ✅ 在线 | 服务器环境 |
13.2 文件复制备份
13.2.1 基本文件复制
# ⚠️ 需要确保没有写入操作
# 方式 1:使用 sqlite3 CLI 的 .backup 命令(推荐)
sqlite3 mydb.db ".backup backup.db"
# 方式 2:文件复制(需确保数据库未被修改)
cp mydb.db backup.db
# 方式 3:带时间戳的备份
sqlite3 mydb.db ".backup backup_$(date +%Y%m%d_%H%M%S).db"
13.2.2 WAL 模式下的备份
# WAL 模式下,需要先 checkpoint 确保所有数据合并到主文件
sqlite3 mydb.db "PRAGMA wal_checkpoint(TRUNCATE);"
cp mydb.db backup.db
# 或者同时复制 WAL 文件(不推荐)
cp mydb.db backup.db
cp mydb.db-shm backup.db-shm
cp mydb.db-wal backup.db-wal
⚠️ 推荐使用
.backup命令而非直接复制文件——.backup会正确处理 WAL 和并发访问。
13.3 CLI .backup 命令
# 备份当前数据库到新文件
sqlite3 mydb.db ".backup backup.db"
# 在交互式 Shell 中使用
sqlite3 mydb.db
sqlite> .backup backup.db
sqlite> .quit
# 备份到指定路径
sqlite3 /var/data/mydb.db ".backup /backup/mydb_$(date +%Y%m%d).db"
13.3.1 从备份恢复
# 方式 1:直接使用备份文件
cp backup.db mydb.db
# 方式 2:使用 .restore 命令
sqlite3 mydb.db ".restore backup.db"
# 方式 3:从 SQL 文件恢复
sqlite3 newdb.db < backup.sql
13.4 SQL 导出与恢复
13.4.1 导出为 SQL
# 导出完整数据库(包括结构和数据)
sqlite3 mydb.db ".dump" > backup.sql
# 只导出特定表
sqlite3 mydb.db ".dump users orders" > tables_backup.sql
# 带 WHERE 条件导出
sqlite3 mydb.db <<EOF > filtered_backup.sql
.headers off
.mode insert
.output stdout
SELECT * FROM users WHERE created_at > '2026-01-01';
EOF
13.4.2 从 SQL 恢复
# 创建新数据库并导入
sqlite3 newdb.db < backup.sql
# 导入到已有数据库(注意可能有冲突)
sqlite3 mydb.db < data.sql
13.4.3 导出为 CSV
# 导出单表为 CSV
sqlite3 mydb.db <<EOF
.headers on
.mode csv
.output users.csv
SELECT * FROM users;
.output stdout
EOF
# 批量导出所有表
for table in $(sqlite3 mydb.db ".tables"); do
sqlite3 mydb.db <<EOF
.headers on
.mode csv
.output ${table}.csv
SELECT * FROM ${table};
.output stdout
EOF
done
13.5 VACUUM 与 VACUUM INTO
13.5.1 VACUUM 基础
-- 基本 VACUUM:重建数据库文件,回收空闲空间
VACUUM;
-- 查看 VACUUM 前后的大小变化
PRAGMA page_count;
PRAGMA freelist_count;
| 效果 | 说明 |
|---|---|
| 回收空间 | 释放 DELETE 产生的空闲页面 |
| 碎片整理 | 重新组织数据页面 |
| 重建索引 | 优化索引结构 |
| 应用新页面大小 | 修改 page_size 后需要 VACUUM |
13.5.2 VACUUM INTO(SQLite 3.27.0+)
-- 将清理后的数据库导出到新文件
VACUUM INTO 'backup.db';
-- 压缩备份:清理 + 备份一步完成
VACUUM INTO '/backup/mydb_20260510.db';
💡
VACUUM INTO是推荐的在线备份方式——原子操作,不会影响当前数据库。
13.5.3 auto_vacuum
-- 设置自动清理模式
PRAGMA auto_vacuum; -- NONE(默认)
PRAGMA auto_vacuum = NONE; -- 不自动清理
PRAGMA auto_vacuum = FULL; -- 完全自动清理
PRAGMA auto_vacuum = INCREMENTAL; -- 增量清理
-- 增量清理(手动触发)
PRAGMA auto_vacuum = INCREMENTAL;
PRAGMA incremental_vacuum(100); -- 清理 100 页
| 模式 | 说明 | 优缺点 |
|---|---|---|
NONE | 不自动清理 | 简单,但需要手动 VACUUM |
FULL | 自动移动页面 | 每次事务后自动清理,写入变慢 |
INCREMENTAL | 需手动触发 | 灵活,可以在低峰期清理 |
13.6 在线备份 API(C API)
13.6.1 API 概述
SQLite 的在线备份 API 允许在数据库打开并使用的情况下进行备份。
// C 语言在线备份示例
#include <sqlite3.h>
int backup_database(const char *source_path, const char *dest_path) {
sqlite3 *source_db, *dest_db;
sqlite3_backup *backup;
int rc;
// 打开源数据库
rc = sqlite3_open(source_path, &source_db);
if (rc != SQLITE_OK) return rc;
// 打开目标数据库
rc = sqlite3_open(dest_path, &dest_db);
if (rc != SQLITE_OK) {
sqlite3_close(source_db);
return rc;
}
// 初始化备份
backup = sqlite3_backup_init(dest_db, "main", source_db, "main");
if (!backup) {
rc = sqlite3_errcode(dest_db);
goto cleanup;
}
// 执行备份(-1 = 全部页面,100 = 每次 100 页)
do {
rc = sqlite3_backup_step(backup, 100);
// 可以在这里添加进度回调
int remaining = sqlite3_backup_remaining(backup);
int total = sqlite3_backup_pagecount(backup);
printf("进度: %d/%d 页\n", total - remaining, total);
} while (rc == SQLITE_OK || rc == SQLITE_BUSY || rc == SQLITE_LOCKED);
// 完成备份
rc = sqlite3_backup_finish(backup);
cleanup:
sqlite3_close(dest_db);
sqlite3_close(source_db);
return rc;
}
13.6.2 API 函数说明
| 函数 | 说明 |
|---|---|
sqlite3_backup_init() | 初始化备份操作 |
sqlite3_backup_step() | 执行一步备份(传输 n 页) |
sqlite3_backup_remaining() | 剩余页数 |
sqlite3_backup_pagecount() | 总页数 |
sqlite3_backup_finish() | 完成备份并释放资源 |
13.6.3 Python 在线备份
import sqlite3
def backup_database(source_path, dest_path):
"""使用 sqlite3 的 iterdump 进行备份"""
source = sqlite3.connect(source_path)
dest = sqlite3.connect(dest_path)
# 方式 1:使用 iterdump
for line in source.iterdump():
dest.execute(line)
dest.commit()
source.close()
dest.close()
def backup_incremental(source_path, dest_path, pages_per_step=100):
"""逐步备份"""
import shutil
# 简单方式:checkpoint + 复制
source = sqlite3.connect(source_path)
source.execute('PRAGMA wal_checkpoint(TRUNCATE)')
source.close()
shutil.copy2(source_path, dest_path)
13.7 定时备份脚本
13.7.1 Bash 定时备份
#!/bin/bash
# sqlite_backup.sh - SQLite 定时备份脚本
DB_PATH="/var/data/mydb.db"
BACKUP_DIR="/backup/sqlite"
KEEP_DAYS=30
# 创建备份目录
mkdir -p "$BACKUP_DIR"
# 生成备份文件名
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/mydb_${TIMESTAMP}.db"
# 执行备份
sqlite3 "$DB_PATH" ".backup ${BACKUP_FILE}"
# 检查备份是否成功
if [ $? -eq 0 ]; then
echo "备份成功: ${BACKUP_FILE}"
# 压缩备份
gzip "${BACKUP_FILE}"
echo "压缩完成: ${BACKUP_FILE}.gz"
else
echo "备份失败!"
exit 1
fi
# 删除过期备份
find "$BACKUP_DIR" -name "*.gz" -mtime +${KEEP_DAYS} -delete
echo "已清理 ${KEEP_DAYS} 天前的备份"
13.7.2 Cron 定时任务
# 每天凌晨 2 点备份
0 2 * * * /path/to/sqlite_backup.sh >> /var/log/sqlite_backup.log 2>&1
# 每小时备份
0 * * * * /path/to/sqlite_backup.sh
# 每周日凌晨 3 点清理并备份
0 3 * * 0 /path/to/sqlite_backup.sh --full
13.8 数据迁移
13.8.1 跨版本迁移
# 导出为 SQL(跨版本安全)
sqlite3 old.db ".dump" > migration.sql
# 编辑 SQL(如需修改表结构)
# sed -i 's/old_column/new_column/g' migration.sql
# 导入到新数据库
sqlite3 new.db < migration.sql
13.8.2 选择性迁移
-- 只迁移特定表
ATTACH DATABASE 'new.db' AS new_db;
CREATE TABLE new_db.users AS SELECT * FROM main.users WHERE created_at > '2025-01-01';
CREATE TABLE new_db.orders AS SELECT * FROM main.orders WHERE user_id IN (SELECT id FROM new_db.users);
DETACH DATABASE new_db;
13.8.3 数据库合并
-- 将 db2 的数据合并到 db1
ATTACH DATABASE 'db2.db' AS db2;
INSERT OR IGNORE INTO main.users SELECT * FROM db2.users;
INSERT OR IGNORE INTO main.orders SELECT * FROM db2.orders;
DETACH DATABASE db2;
13.9 完整性验证
-- 备份前检查
PRAGMA integrity_check; -- 完整检查
PRAGMA quick_check; -- 快速检查
-- 验证备份
sqlite3 backup.db "PRAGMA integrity_check;"
-- 比较源和备份的行数
sqlite3 mydb.db "SELECT 'users', COUNT(*) FROM users UNION ALL SELECT 'orders', COUNT(*) FROM orders;"
sqlite3 backup.db "SELECT 'users', COUNT(*) FROM users UNION ALL SELECT 'orders', COUNT(*) FROM orders;"
⚠️ 注意事项
- 不要在写入时直接复制文件——使用
.backup或VACUUM INTO - WAL 模式下必须先 checkpoint——否则 WAL 中的数据不会包含在复制中
VACUUM需要两倍磁盘空间——重建过程中需要临时空间- 备份文件需要定期验证——检查完整性和可恢复性
- 大数据库的 VACUUM 可能很慢——考虑使用增量备份策略
auto_vacuum = FULL会增加写入开销——在写入密集场景慎用
💡 技巧
VACUUM INTO是最安全的在线备份方式——原子操作,不影响当前数据库- 定期备份 + 增量备份——全量备份每周,增量备份每日
- 备份时同时记录数据库版本——便于迁移时选择正确的 SQLite 版本
- 压缩备份文件——SQLite 数据库压缩率通常很高
- 备份验证应该自动化——在备份脚本中加入完整性检查
📌 业务场景
场景一:Web 应用每日备份
#!/bin/bash
# 每日备份脚本
sqlite3 /var/data/app.db "VACUUM INTO '/backup/app_$(date +%Y%m%d).db'"
gzip /backup/app_$(date +%Y%m%d).db
find /backup -name "*.gz" -mtime +30 -delete
场景二:版本升级迁移
# 1. 备份当前数据库
sqlite3 production.db ".backup pre_upgrade.db"
# 2. 导出为 SQL
sqlite3 production.db ".dump" > migration.sql
# 3. 修改表结构(如有需要)
# vim migration.sql
# 4. 创建新数据库
sqlite3 production_new.db < migration.sql
# 5. 验证
sqlite3 production_new.db "PRAGMA integrity_check;"
# 6. 替换
mv production.db production_old.db
mv production_new.db production.db
🔗 扩展阅读
📖 下一章:14 - 安全 —— 加密(SEE/sqlcipher)、权限、SQL 注入防护