强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

SQLite 完全指南 / 13 - 备份恢复

13 - 备份恢复:.backup、VACUUM 与在线备份 API

13.1 备份策略概览

方法说明是否在线适用场景
文件复制直接复制 .db 文件❌ 需停止写入简单备份
.backup 命令CLI 备份命令✅ 在线CLI 快速备份
SQL 导出.dump 导出为 SQL✅ 在线跨版本迁移
VACUUM INTO清理并导出到新文件✅ 在线压缩备份
在线备份 APIC 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;"

⚠️ 注意事项

  1. 不要在写入时直接复制文件——使用 .backupVACUUM INTO
  2. WAL 模式下必须先 checkpoint——否则 WAL 中的数据不会包含在复制中
  3. VACUUM 需要两倍磁盘空间——重建过程中需要临时空间
  4. 备份文件需要定期验证——检查完整性和可恢复性
  5. 大数据库的 VACUUM 可能很慢——考虑使用增量备份策略
  6. auto_vacuum = FULL 会增加写入开销——在写入密集场景慎用

💡 技巧

  1. VACUUM INTO 是最安全的在线备份方式——原子操作,不影响当前数据库
  2. 定期备份 + 增量备份——全量备份每周,增量备份每日
  3. 备份时同时记录数据库版本——便于迁移时选择正确的 SQLite 版本
  4. 压缩备份文件——SQLite 数据库压缩率通常很高
  5. 备份验证应该自动化——在备份脚本中加入完整性检查

📌 业务场景

场景一: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 注入防护