小巧。快速。可靠。
三选二。
从损坏的 SQLite 数据库中恢复数据

1. 从损坏的 SQLite 数据库中恢复(部分)数据

SQLite 数据库非常可靠。应用程序故障和电源故障通常不会影响数据库内容。但是,可能会损坏 SQLite 数据库。例如,硬件故障可能会损坏数据库文件,或者恶意进程可能会打开数据库并覆盖部分内容。

对于损坏的数据库文件,有时需要尝试从文件中尽可能多地恢复数据。恢复 API 旨在促进此操作。

1.1. 限制

有时可以完美地恢复损坏的数据库,但这只是例外情况。通常,恢复的数据库会在多个方面存在缺陷

恢复 API 会尽力恢复数据库,但结果始终不可靠。有时(例如,如果损坏仅限于索引),恢复将完美地恢复数据库内容。但在其他情况下,恢复将是不完美的。这种不完美的程度会根据应用程序而有所不同。恢复后的书签列表数据库仍然是书签列表。恢复后,一些书签可能丢失、添加或更改,但列表本身“模糊”且不完美,因此增加更多不确定性不会对应用程序造成致命影响。但是,如果会计数据库损坏并在之后恢复,账簿可能不平衡。

最好将恢复 API 视为一项抢救工作。恢复会从旧数据库的残骸中提取尽可能多的可用数据,但部分内容可能已损坏无法修复,因此在将恢复的数据库重新投入使用前应进行一些返工和测试。

2. 使用 CLI 中的 ".recover" 命令进行恢复

手动恢复损坏数据库的最简单方法是使用 SQLite 的命令行界面 或“CLI”。CLI 是一个名为“sqlite3”的程序。使用它以类似于以下命令的方式恢复损坏的数据库文件

sqlite3 corrupt.db .recover >data.sql

这将在名为“data.sql”的文件中生成 SQL 文本,该文本可用于重建原始数据库

sqlite3 recovered.db <data.sql

".recover" 选项实际上是发出给 CLI 的命令。该命令可以接受参数。例如,通过运行

sqlite3 corruptdb ".recover --ignore-freelist" >data.sql

请注意,".recover" 命令及其参数必须包含在引号中。支持以下选项

--ignore-freelist

忽略数据库中似乎属于 freelist 的页面。通常情况下,会扫描 freelist,如果其中包含看起来有内容的页面,就会输出该内容。但是,如果页面确实在 freelist 上,则可能意味着之前删除的信息被重新引入数据库。

--lost-and-found TABLE

如果在恢复过程中发现无法与特定表关联的内容,它会被放入“lost_and_found”表中。使用此选项将“lost_and_found”表的名称更改为“TABLE”。

--no-rowids

如果提供此选项,则不会从损坏的数据库中提取不属于 INTEGER PRIMARY KEY 值的 rowid 值。

3. 将恢复 API 构建到应用程序中

3.1. 源代码文件

如果要将恢复 API 构建到应用程序中,您需要在通常的“sqlite3.c”和“sqlite3.h”源文件之外向构建添加一些源文件。您需要

sqlite3recover.c 这是实现恢复 API 的主要源文件。
sqlite3recover.h 这是与 sqlite3recover.h 对应的头文件。
dbdata.c 此文件实现两个名为“sqlite_dbdata”和“sqlite_dbptr”的虚拟表,这些表是 sqlite3recover.c 所需的。

上面的两个 C 源文件需要与“sqlite3.c”以相同的方式链接到应用程序中。在编译 C 文件时,编译器需要访问头文件。

此外,应用程序(更具体地说,链接到应用程序中的 sqlite3.c)必须使用以下选项进行编译

        -DSQLITE_ENABLE_DBPAGE_VTAB

3.2. 如何实现恢复

以下是从损坏的数据库中恢复内容所需的步骤

  1. 通过调用 sqlite3_recover_init() 或 sqlite3_recover_init_sql() 来创建 sqlite3_recover 处理程序。使用 sqlite3_recover_init() 将恢复的内容存储在单独的数据库中,并使用 sqlite3_recover_init_sql() 生成将重建数据库的 SQL 文本。

  2. 对 sqlite3_recover_config() 进行零次或多次调用,以在新 sqlite3_recovery 处理程序上设置选项。

  3. 重复调用 sqlite3_recover_step(),直到它返回除 SQLITE_OK 之外的任何值。如果它返回 SQLITE_DONE,则恢复操作已完成且没有错误。如果它返回了其他非 SQLITE_OK 值,则发生了错误。sqlite3_recover_run() 接口也可以用作方便的包装器,它只是重复调用 sqlite3_recover_step(),直到它返回除 SQLITE_DONE 之外的任何值。

  4. 分别使用 sqlite3_recover_errcode() 和 sqlite3_recover_errmsg() 接口检索任何错误代码和英文错误消息。

  5. 调用 sqlite3_recover_finish() 来销毁 sqlite3_recover 对象。

接口的详细信息在sqlite3_recover.h 头文件中的注释中描述。

3.3. 示例实现

以下链接显示了 SQLite 本身如何使用恢复扩展的示例