Skip to content

Android数据库修复

John_He edited this page Jun 1, 2017 · 1 revision

修复方法简介

Android 接口支持三种修复方法,如下:

修复方法 简介 相关接口
Repair Kit 解析 B-tree 修复 RepairKit
备份恢复 压缩备份完整数据,使用备份数据恢复 BackupKit
RecoverKit
Dump .dump 命令,已废弃 DBDumpUtil

关于不同修复方法的详细介绍,请参照 《公众号文章》

Repair Kit

使用 Repair Kit 可以直接从损坏的数据库里尽量读出未损坏的数据,不需要事先准备, 但是先备份 Master 信息可以大大增加恢复成功率。 如果有意使用 Repair Kit 恢复数据库, 建议备份 Master 信息。

Repair Kit 使用范例,请参照 sample-repairdb

备份 Master 信息

Master 信息保存了数据库的 Schema,建议每次执行完数据库创建或升级时执行备份,可以保证备份 是最新的。不修改 Schema 的话 Master 信息不会改变。如果你使用 SQLiteOpenHelper,最佳 实践是在 SQLiteOpenHelper.onCreate(...)SQLiteOpenHelper.onUpgrade(...) 的 最后进行备份。

备份 Master 信息只需要调用 RepairKit.MasterInfo.save(...) 即可。备份 Master 信息 典型消耗为几kB ~ 几十kB,几毫秒 ~ 几十毫秒,但如果你有非常非常多的表和索引(万数量级), 这个过程可能会有点慢,建议放在子线程完成。

public class DBHelper extends SQLiteOpenHelper {
    
    public DBHelper(Context context) {
        super(context, DATABASE_NAME, PASSPHRASE, CIPHER_SPEC, null,
                DATABASE_VERSION, ERROR_HANDLER);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        // 执行 CREATE TABLE 创建 Schema
        db.execSQL("CREATE TABLE t1(a,b);");
        db.execSQL("CREATE TABLE t2(c,d);");
        // ......

        // 备份 Master 信息
        RepairKit.MasterInfo.save(db, db.getPath() + "-mbak", BACKUP_PASSPHRASE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // 执行升级
        db.execSQL("ALTER TABLE t1 ADD COLUMN x TEXT;");

        // 备份 Master 信息
        RepairKit.MasterInfo.save(db, db.getPath() + "-mbak", BACKUP_PASSPHRASE);
    }
}

恢复损坏数据库

恢复损坏数据库,首先加载之前备份的 Master 信息(如果有)。

RepairKit.MasterInfo master = RepairKit.MasterInfo.load('/path/to/database.db-mbak', 
        BACKUP_PASSPHRASE, null);
if (master == null) {
    // 加载不成功,可能是不存在或者损坏
}

使用 RepairKit 打开损坏的数据库,使用 SQLiteDatabase 打开新的数据库,调用 output(...) 即可将损坏数据库的内容转移到新数据库。

RepairKit repair = new RepairKit(
        "/path/to/corrupted.db" // 损坏的数据库文件
        PASSPHRASE,             // 数据库密钥(不是备份文件密钥)
        CIPHER_SPEC,            // 加密描述,与打开DB时一样
        master                  // 之前加载的 Master 信息
);

SQLiteDatabase newDb = SQLiteDatabase.openOrCreateDatabase(...);
// 打开新DB用于承载恢复数据,是否加密没所谓

boolean result = repair.output(newDb, 0);
// 输出恢复数据到新DB

if (!result) {
    // 恢复失败
}

repair.release();
// 最后要 release 释放资源

恢复的过程需时较长,请务必在子线程完成,如数据库较大请考虑持有 Wake Lock。

选择性恢复

Repair Kit 可以只恢复一部分表,只需要在 MasterInfo.load(...) 或者 MasterInfo.make(...) 里指定白名单即可。

// 白名单,只有白名单里列到的表才会恢复,表对应的索引也会相应恢复
String[] tables = new String[] {
    "t1", "t2"      // 只恢复 t1 和 t2 两个表
};
RepairKit.MasterInfo master = RepairKit.MasterInfo.load('/path/to/database.db-mbak', 
        BACKUP_PASSPHRASE, tables);

备份和恢复

备份完整数据,损坏后使用备份恢复的方案,如没有备份则无法恢复。由于是备份数据本身而不是 Schema, 备份本身需要经常更新。备份和恢复操作都非常耗时,请勿在主线程操作,备份大数据库和恢复时可以 考虑持有 Wake Lock。

备份

BackupKit backup = new BackupKit(
        db,                         // 要备份的 DB 
        db.getPath() + "-backup",   // 备份文件
        BACKUP_PASSPHRASE,          // 加密备份文件的密钥,非 DB 密钥
        0, null);

int result = backup.run();
switch (result) {
    case BackupKit.RESULT_OK: /* 成功 */ break;
    case BackupKit.RESULT_CANCELED: /* 取消操作 */ break;
    case BackupKit.RESULT_FAILED: /* 失败 */ break;
}

backup.release();

恢复

RecoverKit recover = new RecoverKit(
        db,                         // 要恢复到的目标 DB
        db.getPath() + "-backup",   // 备份文件
        BACKUP_PASSPHRASE           // 加密备份文件的密钥,非 DB 密钥
);

int result = recover.run(false);    // fatal 参数传 false 表示遇到错误忽略并继续,
                                    // 若传 true 遇到错误则中止并返回 FAILED
switch (result) {
    case RecoverKit.RESULT_OK: /* 成功 */ break;
    case RecoverKit.RESULT_CANCELED: /* 取消操作 */ break;
    case RecoverKit.RESULT_FAILED: /* 失败 */ break;
}

recover.release();

取消操作

由于备份和恢复都比较耗时,WCDB 提供接口中止备份或恢复操作。只需要在另外的线程调用 BackupKit.cancel()RecoverKit.cancel() 即可通知备份或恢复线程中止并尽快返回, 返回码为 RESULT_CANCELED

Clone this wiki locally
You can’t perform that action at this time.