小巧。快速。可靠。
三者不可兼得。
伪造表

1. 简介

伪造表是一种与索引附加到同一个 B 树 上的表。伪造表允许查询或修改索引的内容,就好像索引是普通表一样。

伪造表仅用于分析和调试。这并非大多数应用程序开发人员应该理解或甚至知道的特性。伪造表仅供专家使用。

不当使用伪造表会导致索引损坏,但可以通过运行 REINDEX 来修复以这种方式造成的任何损坏。

2. 详情

SQLite 中的每个表和每个索引都存储在数据库文件中的一个单独的 B 树中。每个 B 树都由其根页号标识。任何索引或表的根页号可以通过查询 sqlite_schema 表 的 "rootpage" 列来找到。有关此设计的更多背景信息,请参阅 索引教程文件格式 文档。

通常,表和索引的 B 树略有不同。表 B 树包含一个 64 位整数键和任意数据。64 位整数键是 ROWID。索引 B 树包含一个任意二进制键,没有数据。因此,表 B 树和索引 B 树并不直接兼容。

但是,WITHOUT ROWID 表的 B 树与索引 B 树的格式相同。因此,可以将索引 B 树访问为 WITHOUT ROWID 表。

2.1. 手动创建的伪造表

创建伪造表的一种方法是直接编辑 sqlite_schema 表,以插入描述该表的新行。例如,假设模式如下所示

CREATE TABLE t1(a INTEGER PRIMARY KEY,b TEXT,c INT, d INT);
CREATE INDEX t1bc ON t1(b,c);

与 t1bc 索引具有相同结构的 WITHOUT ROWID 表将如下所示

CREATE TABLE t2(b TEXT,c INT,a INT, PRIMARY KEY(b,c,a)) WITHOUT ROWID;

要针对 t1bc 索引创建一个永久性伪造表 "t2",首先应通过运行 "PRAGMA writable_schema=ON" 来启用 sqlite_schema 表的编辑。(请务必注意此 PRAGMA 伴随的警告。错误会导致严重数据库损坏。)然后,将新条目插入 sqlite_schema 表,如下所示

INSERT INTO sqlite_schema(type,name,tbl_name,rootpage,sql)
 SELECT 'table','t2','t2',rootpage,
   'CREATE TABLE t2(b,c,a,PRIMARY KEY(b,c,a))WITHOUT ROWID'
   FROM sqlite_schema
  WHERE name='t1bc';

上面的 INSERT 语句在 sqlite_schema 表中添加了一行新数据,该数据定义了一个名为 "t2" 的表,该表具有与 t1bc 索引相同的磁盘格式,并指向同一个 B 树。添加此 sqlite_schema 表条目后,有必要关闭并重新打开数据库,以便让 SQLite 重新读取模式。然后,可以查询 "t2" 表以查看 "t1bc" 索引的内容。

2.1.1. 损坏的数据库

上述手动伪造表方法的一个严重问题是,在将新的 "t2" 条目添加到 "sqlite_schema" 表后,数据库文件在技术上将是损坏的。 "t1bc" 索引和 "t2" 表都将指向同一个 B 树。这不会立即造成任何问题,但应避免运行 VACUUM

可以写入 "t2" 表,从而更改索引的内容。但这样做会导致 "t1bc" 索引与其父表 "t1" 不同步。不同步的索引会导致查询结果错误。

由于 "t2" 伪造表是一种数据库损坏的形式,因此不推荐使用手动创建伪造表的方法。实际上,任何使用伪造表的操作都只建议在专家开发人员中使用,但手动创建的伪造表尤其不建议使用,因为它们是永久性的。

2.2. 瞬态伪造表

创建伪造表的另一种(更安全)方法是在 SQLite 的内部符号表中添加一个伪造表的条目,而不更新磁盘上的 "sqlite_schema" 表。这样,伪造表仅存在于单个数据库连接中,并且会在每次重新加载模式时自动删除。

创建瞬态伪造表涉及一个特殊的 sqlite3_test_control() 调用。与所有其他 SQLite API 不同,sqlite3_test_control() 接口会因版本而异,因此,下面描述的机制不能保证在未来版本的 SQLite 中有效。SQLite 开发人员并不认为这是一个问题,因为伪造表不应在应用程序中使用。伪造表仅用于分析和测试。

要创建瞬态伪造表,首先按如下方式调用 sqlite3_test_control()

sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 1, tnum);

"db" 参数是指向 数据库连接 的指针。 "main" 参数是要在其中创建伪造表的模式的名称。 "1" 参数启用伪造表机制。 "tnum" 是伪造表应镜像的索引的根页。

在执行上述 sqlite3_test_control() 调用后,运行一个 CREATE TABLE 语句来定义伪造表。在启用伪造表机制的情况下,此 CREATE TABLE 语句不会创建真实的表,而是仅仅在 SQLite 的内部符号表中添加一个条目。请注意,CREATE TABLE 语句必须采用与索引相匹配的正确格式。如果伪造表的列数不正确,或者不是 WITHOUT ROWID 表,或者与索引 B 树不兼容,则在使用伪造表时会导致 SQLITE_CORRUPT 错误。

运行 CREATE TABLE 语句后,按如下方式禁用伪造表机制

sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 0, 0);

换句话说,执行相同的 sqlite3_test_control() 调用,但将最后两个参数更改为零。

在将伪造表加载到 SQLite 的内部模式中(如上所述)后,就可以将伪造表用作任何其他表。但伪造表仅对创建它的那个数据库连接可见。磁盘上的数据库文件中不会进行任何更改。并且伪造表将在下次加载模式时消失。

2.3. “.imposter” shell 命令

从 SQLite 3.16.0(2017-01-02)开始,命令行 shell 包含一个点命令 " .imposter",它可以完成设置瞬态伪造表的所有工作。无需多次调用 sqlite3_test_control() 并找出并调用兼容的 CREATE TABLE 语句,可以按如下方式构造瞬态伪造表

.imposter t1bc t2

当然,将示例中显示的 "t1bc" 和 "t2" 替换为所需的索引和伪造表名称。 ".imposter" 命令读取 "t1bc" 索引的模式,使用该信息为伪造表构造一个兼容的 CREATE TABLE 语句,然后执行所有必要的调用来自动创建瞬态伪造表。

3. 总结和最终警告

伪造表机制是 SQLite 的一个功能强大的分析和调试工具。但与所有锋利的工具一样,它也可能很危险,如果使用不当会导致数据库文件损坏。不要尝试在应用程序中使用伪造表。伪造表仅供专家在实验室使用。

此页面最后修改于 2022-01-08 05:02:57 UTC