1 RBU扩展
RBU扩展是SQLite的一个附加组件,设计用于大型 网络边缘低功耗设备上的SQLite数据库文件。RBU 可用于两个单独的任务:
RBU更新操作 .一个 RBU更新 是的批量更新 可能包括许多插入、更新和删除的数据库文件 对一个或多个表的操作。 RBU真空操作 .一个 RBU真空 优化和重建 整个数据库文件,结果类似于SQLite的原生VACUUM 命令。
首字母缩写RBU代表“可恢复批量更新”。
这两个RBU功能都可以使用SQLite的内置 SQL命令-通过一系列的RBU更新 插入 , 删除 和 更新 单个事务中的命令和单个事务的RBU真空 真空 命令。 RBU模块具有以下优点 这些简单的方法:
RBU可能更高效
将更改应用于B-Tree(数据结构)的最有效方法 SQLite用于将每个表和索引存储在磁盘上)是为了 更改键顺序。 但如果SQL表有一个或多个索引,则键 每个索引的顺序可能与主表和其他索引的顺序不同 辅助指标。 因此,在执行一系列 插入 , 更新 和 删除 语句,通常不可能对 操作,使得所有b树都按密钥顺序进行更新。 RBU更新 过程通过将所有更改应用于一个主表来解决此问题 通过,然后在单独的过程中将更改应用于每个索引,确保每个 优化更新B-Tree。 对于大型数据库文件(不 适合操作系统磁盘缓存)此过程可以导致以下两个顺序 更新速度更快。
RBU真空操作需要较少的临时磁盘空间和写入 到磁盘的数据比SQLite VACUUM少。 SQLite VACUUM大约需要 要运行的临时磁盘空间中的最终数据库文件的两倍大小。 写入的数据总量大约是 最终数据库文件。 相比之下,RBU真空需要大致的尺寸 在临时磁盘空间中写入最终数据库文件,总共写入 两倍于磁盘。
另一方面,RBU真空吸尘器比普通SQLite使用更多的CPU 真空-在一次测试中,真空度是真空度的五倍。 因此,RBU 在相同条件下,真空通常比SQLite真空慢得多 条件。
RBU在后台运行
正在进行的RBU操作(更新或真空)不会 干扰对数据库文件的读取访问。
RBU递增运行
RBU操作可能会暂停,然后恢复,可能是 干预性停电和/或系统重置。 对于RBU更新 原始数据库内容对所有数据库阅读器保持可见,直到 已应用整个更新-即使更新被挂起并且 后来又恢复了。
默认情况下未启用RBU扩展。 要启用它,请编译 合并 使用 SQLITE_ENABLE_RBU数据库 compile-time选项。
2 RBU更新
2.1. RBU更新限制
以下限制适用于RBU更新:
更改必须包括 插入 , 更新 、和 删除 仅限操作。 CREATE和DROP操作不是 支持。
插入 语句不能使用默认值。
更新 和 删除 语句必须标识目标行 通过rowid或非NULL PRIMARY KEY值。
更新 语句不能修改PRIMARY KEY或rowid值。
RBU更新不能应用于任何包含列的表 名为“rbu_control”。
RBU更新不会触发任何触发器。
RBU更新不会检测或阻止外键或 CHECK约束冲突。
所有RBU更新都使用“OR ROLLBACK”约束处理机制。
目标数据库可能不在中 WAL模式 .
目标数据库不能包含 表达式上的索引 . 支持从SQLite 3.30.0开始的表达式索引 (2019-10-04). 当 正在应用RBU更新。 目标上保持读锁 数据库来防止这种情况。
2.2. 准备RBU更新文件
RBU应用的所有更改都存储在单独的SQLite数据库中 称为“RBU数据库”。 要修改的数据库称为 “目标数据库”。
对于目标数据库中将被更新修改的每个表, 在RBU数据库中创建相应的表。 RBU数据库 表模式与目标数据库的模式不同,但它是派生的 从它作为 如下所述 .
RBU数据库表包含每个目标数据库的一行 通过更新插入、更新或删除行。 填充RBU数据库 表在中进行了描述 以下部分 .
2.2.1、。 RBU数据库模式
对于目标数据库中的每个表,RBU数据库应包含一个表 命名的”数据< 整数 >_< 目标表名称 >“其中 < 目标表名称 >是目标中表的名称 数据库和< 整数 >是零或多个数字的任意序列 字符(0-9)。 RBU数据库中的表按顺序由 名称(根据BINARY排序顺序从最小到最大), 因此,目标表的更新顺序受选择的影响 的< 整数 >data_%表名的一部分。 虽然这可以 使用RBU更新时非常有用 某些类型的虚拟表 ,通常没有 使用空字符串以外的任何内容来代替的原因 < 整数 >.
data_%表必须与目标表具有相同的所有列,加上 另一列名为“rbucontrol”。 data_%表应该没有 PRIMARY KEY或UNIQUE约束,但每列的类型应与 目标数据库中的相应列。 rbu_control列应该 根本没有类型。 例如,如果目标数据库包含:
然后RBU数据库应包含:
创建表格data_t1(a整数,b文本,c,rbu_control);
data_%表中列的顺序无关紧要。
如果目标数据库表是虚拟表或没有 PRIMARY KEY声明,data_%表还必须包含一列 名为“rbu_rowid”。 rbu_rowid列映射到表 粗鲁的 . 例如,如果目标数据库包含以下内容之一:
使用fts3(a,b)创建虚拟表x1; 创建表x1(a,b);
则RBU数据库应包含:
创建表格数据_x1(a,b,rbu_rowid,rbu_control);
“rowid”列对其执行操作的虚拟表 not的功能类似于无法使用RBU更新主键值。
的所有非隐藏列(即“SELECT*”匹配的所有列) 输入表中必须存在目标表。 对于虚拟表, 隐藏列是可选的-如果在中存在,则由RBU更新 输入表,否则不显示。 例如,写入fts4 带有隐藏languageid列的表,例如:
使用fts4(a,b,languageid='langid')创建虚拟表ft1;
可以使用以下任一输入表模式:
CREATE TABLE data_ft1(a、b、langid、rbu_rowid、rbu_control); 创建表格data_ft1(a,b,rbu_rowid,rbu_control);
2.2.2. RBU数据库内容
对于要作为RBU的一部分插入到目标数据库中的每一行 更新时,相应的data_%表应包含单个记录 将“rbucontrol”列设置为包含整数值0。 这个 其他列应设置为构成新记录的值 插入。
“rbu_control”列也可以设置为整数值2 插入。 在这种情况下,新行以静默方式替换任何现有行 具有相同的主键值。 这相当于DELETE后跟一个 使用相同的主键值INSERT。 它与SQL REPLACE不同 命令,因为在这种情况下,新行可以替换任何冲突的行(即。 那些由于UNIQUE约束或索引而冲突的),而不仅仅是那些 主键冲突。
如果目标数据库表具有INTEGER PRIMARY KEY,则不是 可以在IPK列中插入NULL值。 正在尝试 这样会导致SQLITE_MISMATCH错误。
对于要作为RBU的一部分从目标数据库中删除的每一行 更新,相应的data_%表应包含一条记录 将“rbucontrol”列设置为包含整数值1。 这个 要删除的行的实际主键值应存储在 data_%表的相应列。 存储在 不使用其他列。
对于要作为RBU的一部分从目标数据库更新的每一行 更新时,相应的data_%表应包含单个记录 将“rbucontrol”列设置为包含文本类型的值。 标识要更新的行的真正主键值应该是 存储在data_%表行的相应列中 正在更新的所有列的新值。 中的文本值 “rbu_control”列必须包含与相同数量的字符 目标数据库表中有列,并且必须完全包含 “x”和“.” 字符(或在某些特殊情况下为“d”-见下文)。 对于 每个正在更新的列,相应的字符设置为 “x”。 对于保持原状的对象 rbu_control值应设置为“.”。 例如,给定表格 上面的更新语句:
由以下创建的data_t1行表示:
插入data_t1(a,b,c,rbu_control)值(4,NULL,'usa','..x');
如果使用RBU更新目标数据库中的大BLOB值 可以更有效地存储可用于修改的补丁或增量 现有BLOB,而不是RBU数据库中的全新值。 RBU允许以两种方式指定增量:
化石增量格式只能用于更新BLOB值。 相反 通过将新的BLOB存储在data_%表中,化石delta被存储 而不是。 而不是将“x”指定为rbu_control字符串的一部分 对于要更新的列,将存储一个“f”字符。 处理时 “f”更新,RBU从磁盘加载原始BLOB数据,应用化石 增量并将结果存储回数据库文件。 RBU 数据库由生成 sqldiff—rbu 利用化石三角洲 这样做可以节省RBU数据库中的空间。
要使用自定义增量格式,RBU应用程序必须注册一个 开始处理 更新。 将使用两个参数调用rbu_delta()-原始值 存储在目标表列中,并将增量值作为 RBU更新。 它应该返回将增量应用于 原始价值。 要使用自定义增量函数 与要更新的目标列对应的rbu_control值必须为 设置为“d”而不是“x”。 然后,使用 值存储在相应的data_%列中,RBU调用用户定义的 SQL函数“rbu_delta()”和目标表列中的存储。
例如,此行:
插入data_t1(a,b,c,rbu_control)值(4,NULL,'usa','..d');
导致RBU更新目标数据库表的方式类似于:
更新t1集合c=rbu_delta(c,'usa'),其中a=4;
如果目标数据库表是虚拟表或没有PRIMARY的表 KEY,rbu_control值不应包含对应的字符 到rburowid值。 例如:
插入data_ft1(a,b,rbu_rowid,rbu_control) 值(NULL,'usa',12,'.x');
导致类似以下结果:
更新ft1集合b='usa',其中rowid=12;
data_%表本身应该没有PRIMARY KEY声明。 然而,如果从每个数据中读取行_%,RBU的效率会更高 按“rowid”顺序排列的表与读取它们的顺序大致相同 相应目标数据库表的PRIMARY KEY。 在其他 单词,行应使用目标表PRIMARY KEY进行排序 字段插入data_%表之前。
2.2.3. 使用带FTS3/4表的RBU
通常 FTS3或FTS4 表是虚拟表的一个示例 具有类似于主键的rowid。 因此,对于以下FTS4表:
使用fts4(addr,text)创建虚拟表格ft1; 使用fts4创建虚拟表ft2;-- 隐式“内容”列
data_%表可以创建如下:
使用fts4(addr、text、rbu_rowid、rbu_control)创建表格data_ft1; 使用fts4创建表data_ft2(内容,rbu_rowid,rbu_control);
并进行填充,就像目标表是没有 显式PRIMARY KEY列。
无争议的FTS4表格 处理方式类似, 除非在以下情况下,任何更新或删除行的尝试都会导致错误 应用更新。
外部内容FTS4表 也可能是 使用RBU更新。 在这种情况下,用户需要配置RBU 数据库,以便相同的UPDATE、DELETE和INSERT操作集 应用于FTS4索引和基础内容表。 至于所有人 更新外部内容FTS4表,用户还需要确保 之前对FTS4索引应用任何UPDATE或DELETE操作 它们应用于基础内容表(请参阅FTS4文档 详细说明)。 在RBU中,通过确保名称 用于写入FTS4表的data_%表的排序在名称之前 用于使用 二进制的 排序序列。 为了避免在 RBU数据库,可以使用SQL视图来代替其中一个data_%表。 例如,对于目标数据库模式:
创建表格ccc(地址,文本); 使用fts4(addr,text,content=ccc)创建虚拟表ccc_fts;
可以使用以下RBU数据库模式:
创建表格data_ccc(addr,text,rbu_rowid,rbu_control); 将视图data0_ccc_fts创建为SELECT*FROM data_ccc;
然后可以使用预期的更新正常填充data_ccc表 用于目标数据库表ccc。 RBU将从中读取相同的更新 data0_ccc_fts视图并应用于fts表ccc_fts。 因为 “data0_ccc_fts”小于“data_ccc”,将更新fts表 首先,根据需要。
底层内容表具有显式INTEGER PRIMARY的情况 KEY列稍微困难一些,因为存储在 rbu_control列与FTS索引及其 基础内容表。 对于基础内容表,字符 必须包含在显式IPK的任何rbu_control文本值中,但 对于具有隐式rowid的FTS表本身,它不应该这样做。 这个 不方便,但可以使用更复杂的视图解决,如下所示:
--目标数据库架构 创建表ddd(i整数主键,k文本); 使用fts4(k,content=ddd)创建虚拟表ddd_fts; --RBU数据库模式 创建表格data_ccc(i,k,rbu_control); 将视图数据0_ccc_fts创建为SELECT i AS rbu_rowid,k,CASE 当rbu_control IN(0,1)时,rbu_ccontrol ELSE substr(rbu_conrol,2)END 来自data_ccc;
上述SQL视图中的substr()函数返回 带有第一个字符的rbu_control参数(对应于 删除FTS表中不需要的“i”列)。
2.2.4. 使用sqldiff自动生成RBU更新
截至SQLite 版本3.9.0 (2015-10-14), 这个 SQL差异 实用程序能够生成 RBU数据库表示具有 相同的模式。 例如,以下命令:
输出SQL脚本以创建RBU数据库,如果用于更新 数据库t1.db,对其进行修补,使其内容与 数据库t2.db。
默认情况下,sqldiff会尝试处理 提供给它的两个数据库。如果一个数据库中出现任何表 但不是另一个,或者如果任何表中的模式略有不同 一个数据库它是一个错误。 如果出现以下情况,“--table”选项可能有用 导致问题
sqldiff默认忽略虚拟表。 然而,这是可能的 为具有以下特性的虚拟表显式创建RBU data_%表 一个rowid,其功能类似于使用以下命令的主键:
sqldiff--rbu--表< 虚拟表格名称 >t1.db t2.db(t1.db)
不幸的是,即使默认情况下忽略虚拟表 基础数据库表 他们为了 数据库中的存储数据不是,并且 SQL差异 将包括添加这些 任何RBU数据库。 因此,用户尝试使用sqldiff 创建RBU更新以应用于具有一个或多个虚拟数据库的目标数据库 表可能必须使用--table选项单独运行sqldiff 在目标数据库中更新每个表。
2.3. RBU更新C/C++编程
RBU扩展接口允许应用程序应用RBU更新 将存储在RBU数据库中的数据转换为现有目标数据库。 程序如下:
使用sqlite3rbu_Open(T,A,S)函数打开RBU句柄。
T参数是目标数据库文件的名称。 A参数是RBU数据库文件的名称。 S参数是用于存储的“状态数据库”的名称 中断后恢复更新所需的状态信息。 S参数可以为NULL,在这种情况下,状态信息 存储在RBU数据库中的各个表中,这些表的名称都是 以“rbu”开头。
sqlite3rbu_open(T,A,S)函数返回指向 “sqlite3rbu”对象,然后将其传递给后续对象 接口。
向数据库注册任何必需的虚拟表模块 sqlite3rbu_db(X)返回的句柄(其中参数X是sqlite3rdbu 从sqlite3rbu_open()返回的指针)。 此外,如果需要,请注册 rbu_delta()SQL函数使用 sqlite3_create_function_v2() .
在上调用sqlite3rbu_step(X)函数一次或多次 sqlite3rbu对象指针X。每次调用sqlite3rbu _step() 执行单个b-tree操作,因此可能会有数千个调用 需要应用完整更新。 sqlite3rbu_step() 更新完成后,接口将返回SQLITE_DONE 完全应用。
调用sqlite3rbu_close(X)销毁sqlite3rdbu对象指针。 如果调用sqlite3rbu_step(X)的次数足够多 将更新应用于目标数据库,然后应用于RBU数据库 标记为完全应用。 否则,RBU的状态 更新应用程序保存在状态数据库中(或RBU中 数据库,如果sqlite3rbu_open()中的状态数据库文件的名称 为NULL),以便以后恢复更新。
如果更新仅由 调用sqlite3rbu_close()时,保存状态信息 在状态数据库中(如果存在),或在RBU数据库中。 这允许后续流程自动 从停止的位置恢复RBU更新。 如果状态信息存储在RBU数据库中,则可以将其删除 删除名称以“rbu”开头的所有表。
有关更多详细信息,请参阅中的注释 头文件 平方米3rbu。 小时 .
三。 RBU真空
3.1. RBU真空限制
与SQLite的内置VACUUM命令相比,RBU VACUUM具有 以下限制:
3.2. RBU真空C/C++编程
本节概述了演示 将RBU真空集成到应用程序中。 有关详细信息, 请参阅中的注释 头文件 平方米3rbu。 小时 .
RBU真空应用都实现了以下一些变化 程序:
通过调用sqlite3rbu_vacution(T,S)创建RBU句柄。
参数T是要清空的数据库文件的名称。 参数S为 如果 真空操作暂停。
如果sqlite3rbu_vacuum()为 调用时,它将自动创建并填充单个表 用于存储RBU真空的状态-“RBU_state”。 如果正在进行RBU 真空暂停,此表中填充了状态数据。 下一个 使用相同的S参数调用sqlite3rbu_vacuum()时,它会检测 此数据并尝试恢复暂停的真空操作。 什么时候? RBU真空操作完成或遇到错误,RBU 自动删除rbustate表的内容。 在这种情况下, 下一次调用sqlite3rbuvacuum()将启动一个全新的真空 从头开始操作。
建立一个确定RBU的公约是一个好主意 基于目标数据库名称的真空状态数据库名称。 这个 下面的示例代码使用“<target>-真空”,其中<target>为 正在清空的数据库的名称。
数据库中索引使用的任何自定义排序规则序列 使用返回的两个数据库句柄进行注册 通过sqlite3rbu_db()函数。
在RBU句柄上调用函数sqlite3rbu_step(),直到 RBU真空完成,出现错误或应用程序希望 暂停RBU真空。
每次调用sqlite3rbu_step()都会对 完成真空操作。 根据数据库的大小 单个真空可能需要数千次调用sqlite3rbustep()。 如果真空操作具有 如果真空操作尚未完成但没有错误,则返回SQLITE_OK 发生,如果遇到错误,则返回SQLite错误代码。 如果 如果发生错误,则立即调用sqlite3rbu_step() 返回相同的错误代码。
最后,调用sqlite3rbu_close()来关闭RBU句柄。 如果 应用程序在真空之前停止调用sqlite3rbu_step() 完成或发生错误时,真空状态保存在 状态数据库,以便稍后恢复。
与sqlite3rbu_step()类似,如果真空操作已完成, sqlite3rbu_close()返回SQLITE_DONE。 如果真空尚未完成 但没有发生错误,返回SQLITE_OK。 或者,如果出现错误 发生时,返回SQLite错误代码。 如果作为一部分发生错误 在之前调用sqlite3rbu_step()的过程中,sqlite3rbu_close()返回 相同的错误代码。
下面的示例代码说明了上述技术。
/*
**启动新RBU真空或恢复暂停的RBU真空
**数据库zTarget。 当任一错误发生时返回RBU
**真空完成或应用程序发出中断信号时
**(代码未显示)。
**
**如果RBU真空成功完成,则返回SQLITE_DONE。
**如果发生错误,则返回SQLite错误代码。 或者,如果应用程序
**发出中断信号,暂停RBU真空操作,以便
**可以通过对此函数的后续调用恢复,并返回
**SQLITE_OK。
**
**此函数使用名为“<zTarget>-dvacution”的数据库
**状态数据库,其中<zTarget>是数据库的名称
**正在吸尘。
*/ int do_rbu_vacuum(const char*zTarget){ 整数rc; char*zState; /*状态数据库名称*/ sqlite3rbu*pRbu; /*RBU真空手柄*/ zState=sqlite3_mprintf(“%s真空”,zTarget); 如果(zState==0)返回SQLITE_NOMEM; pRbu=sqlite3rbu_vacuum(zTarget,zState); sqlite3_free(zState); 如果(pRbu){ sqlite3*dbTarget=sqlite3rbu_db(pRbu,0); sqlite3*dbState=sqlite3rbu_db(pRbu,1); /*目标数据库使用的任何自定义排序规则序列都必须
**在这里注册两个数据库句柄*/ while(sqlite3rbu_step(pRbu)==SQLITE_OK){ 如果( <应用程序已发出中断信号> )断裂; } } rc=sqlite3rbu_close(pRbu); 返回rc; }