libSQL 中的 JSONB 二进制格式:SQLite 原生 JSON 二进制编码规范与源码级解读

发布时间:2026/9/14 5:15:25
libSQL 中的 JSONB 二进制格式:SQLite 原生 JSON 二进制编码规范与源码级解读
libSQL 中的 JSONB 二进制格式SQLite 原生 JSON 二进制编码规范与源码级解读【免费下载链接】libsqllibSQL is a fork of SQLite that is both Open Source, and Open Contributions.项目地址: https://gitcode.com/GitHub_Trending/li/libsqlJSONBJSON Binary是 SQLite 从 3.45.0 版本起引入的一种 JSON 二进制编码格式libSQLlibsql-sqlite3完整继承了这一能力。它把 RFC 8259 文本 JSON 的标点符号替换为紧凑的二进制头使数据体积通常缩小 5%10%解析耗用的 CPU 周期不足文本 JSON 的一半。本文以 jsonb.md 为骨架结合 json.c 源码与 jsonb01.test 测试用例完整讲解 JSONB 的编码规范、元素类型表、头字节布局及设计动机帮助读者理解 JSONB 作为 BLOB 的字节级语义、为何它能直接充当 JSON 函数的内部解析树以及如何在 libSQL 中正确使用jsonb_*系列 SQL 函数。1. JSONB 是什么JSONB 是 SQLite3.45.0约 2024-01-01 起提供的 JSON 替代二进制编码以BLOB形式存储。相比普通文本 JSONJSONB 有两个核心优势体积更小大多数情况下比对应文本 JSON 小 5%10%处理更快解析所需 CPU 周期不足文本 JSON 的一半。SQLite 内置的 JSON SQL 函数 对于任何 JSON 输入参数都可以同时接受普通文本 JSON 或二进制 JSONB。这意味着你既可以用json(...)生成文本也可以用jsonb(...)生成二进制二者的输出都可以作为其他 JSON 函数json_extract、json_set、json_patch等的输入。JSONB 这个名称借鉴自 PostgreSQL但两者的磁盘格式完全不同同名不同物内部表示差异极大二进制层面完全不兼容。1.1 核心思想用头取代标点JSONB 规范的中心思想是每个元素都以一个包含大小 类型的头header开始。这个头取代了文本 JSON 中的双引号、花括号、方括号、逗号和冒号等标点符号。由于每个元素的大小和类型都写在头里解析时不再需要向前扫描寻找闭合分隔符读取速度因此大幅提升。JSONB 的 payload负载与对应文本 JSON 完全一致——相同的 payload 字节以相同的顺序出现。JSONB 与普通文本 JSON 唯一的实质区别是JSONB 为每个元素附加二进制头并省略了所有分隔与标点符号。1.2 仅供内部使用JSONB 的细节不面向应用开发者。对应用来说JSONB 应被视为 SQLite 内部使用的不透明 BLOB。应用只能通过 JSON SQL 函数 访问 JSONB不应直接查看 BLOB 的字节。然而 JSONB 格式要求跨所有未来 SQLite 版本向后兼容——升级 SQLite 时你不必导出再重新导入数据库文件。正因为如此格式必须精确定义。这份 jsonb.md 的地位类似于描述 SQLite 数据库文件磁盘格式的文档不是为了鼓励应用直接读写字节而是为了让格式稳定、持久、可移植。2. 编码规则Header PayloadJSONB 是底层文本 JSON 的直接翻译区别仅在于采用更易解析的二进制编码。每个 JSON 元素编码为Header头19 字节决定元素类型字符串、数值、布尔、null、对象、数组以及 payload 大小Payload负载0 字节到 SQLite 允许的最大 BLOB 大小。2.1 Payload 大小头的高 4 位头第一个字节的高 4 位决定头的总大小并可能同时决定 payload 大小高 4 位值含义011头恰好 1 字节payload 大小直接由这 4 位给出011 字节12头共 2 字节payload 大小为后续 1 字节无符号大端整数13头共 3 字节payload 大小为后续 2 字节无符号大端整数14头共 5 字节payload 大小为后续 4 字节无符号大端整数15头共 9 字节payload 大小为后续 8 字节无符号大端整数当前 SQLite 设计不支持超过 2GiB 的 BLOB因此 8 字节高 4 位为 15的 payload 大小变体永远不会被现有代码使用——它在规范中保留是为了将来扩展。元素头不必采用最简形式。以 JSON 数值1为例它可以被编码为五种不同形式0x13 0x31 0xc3 0x01 0x31 0xd3 0x00 0x01 0x31 0xe3 0x00 0x00 0x00 0x01 0x31 0xf3 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x01 0x31最短编码当然更受青睐数字这类基本元素通常就用最短形式。但数组或对象的总大小在生成元素头时可能尚不可知构造者可以预留最大可能的头空间等结束时再回头填上正确的 payload 大小。这种技巧可能导致数组/对象的头比绝对必要值更大。这一点在源码 json.c 中体现得很清楚jsonBlobAppendNode按 1/2/3/5 字节的档位写出头部jsonBlobChangePayloadSize则在对象或数组闭合时回头修正头字节与负载大小。2.2 元素类型头的低 4 位头第一个字节的低 4 位首字节掩码0x0f决定元素类型。文档按 115 编号列出而实际存储在低 4 位中的编码值域为 012列表编号 1 对应编码值 0依此类推与源码中 json.c 的宏定义一一对应列表编号实际编码值源码宏说明10JSONB_NULLJSONnull真实 null 的 payload 大小必须为 0类型 0 但大小非 0 的元素留给未来扩展旧版本会把它当 null 解释以保持向后兼容21JSONB_TRUEJSONtrue真实值 payload 大小必须为 0类型 1 但大小非 0 保留扩展旧实现继续按true解释32JSONB_FALSEJSONfalse规则同上43JSONB_INT符合 RFC 8259 规范格式的 JSON 整数payload 为该数值的 ASCII 文本54JSONB_INT5非规范格式的 JSON 整数如 JSON5 十六进制0x000记法payload 为 ASCII 文本转成文本 JSON 时需翻译65JSONB_FLOAT符合 RFC 8259 的浮点数payload 为 ASCII 文本76JSONB_FLOAT5非规范的浮点数如 JSON5 扩展转文本 JSON 时需翻译87JSONB_TEXT不含任何转义、也不含需在 SQL/JSON 中转义字符的 JSON 字符串payload 为 UTF-8 文本不含引号定界符98JSONB_TEXTJ含 RFC 8259 转义如\n、\u0020的字符串被json_extract提取进 SQL 时需把转义翻译为真实 UTF-8payload 不含定界符109JSONB_TEXT5含转义包括 JSON5 特有、RFC 8259 没有的转义的字符串渲染为文本前需翻译为标准 JSON提取进 SQL 时翻译为真实 UTF-81110JSONB_TEXTRAW含若渲染为标准 JSON 文本则必须转义的 UTF-8 字符的字符串payload 不含定界符1211JSONB_ARRAYJSON 数组payload 为构成数组元素的若干 JSONB 元素1312JSONB_OBJECTJSON 对象payload 为成对的 JSONB 元素每对第一个元素必须是字符串类型 710第二个元素可以是任意类型包括嵌套数组/对象1413JSONB_RESERVED-14保留旧实现遇到应报错1514JSONB_RESERVED-15保留旧实现遇到应报错类型编码超出 012 的范围即编码值 13、14以及未使用的 15全部保留给未来扩展。当前实现遇到上述列表之外的类型会报错未来版本可能利用这些剩余类型实现索引或类似优化加速对大型 JSON 数组/对象的查找。2.3 元素类型的设计动机懒转换JSONB 的一个关键目标是从文本 JSON 与 SQL 值之间快速互转。从文本转 JSONB 时不希望转换子程序把 CPU 周期浪费在把元素值标准化为某种可能永远用不到的格式上——格式转换是**懒的**推迟到真正需要时才发生。这带来两个直接影响数值以文本存储而非数字payload 是从文本 JSON 直接拷贝的数值文本省去了转换按来源细分多种元素类型INT纯 RFC 8259 整数与 INT5JSON5 十六进制等扩展分开FLOAT 与 FLOAT5 分开字符串按来源与转义方式细分出四种表示JSONB_TEXT/JSONB_TEXTJ/JSONB_TEXT5/JSONB_TEXTRAW。第二个目标是让 JSONB 直接充当 JSON 函数处理 JSON 值时的**解析树**。在 JSONB 出现之前json_replace()、json_patch()等操作分三步把文本 JSON 翻译成便于扫描和编辑的内部格式对 JSON 执行请求的操作把内部格式翻译回文本。JSONB 的目标就是直接作为这种内部格式跳过第 1 步和第 3 步。而大部分 CPU 周期恰恰消耗在第 1、3 步上——这就是 JSONB 处理速度远快于文本 JSON 的原因处理 JSONB 只需执行三步中的第 2 步。既然 JSONB 要充当内部二进制表示数值以文本存储就又多了一条理由可以最小化第 1、3 步所需的转换工作。字符串的四种表示也同理——不同来源RFC 8259 JSON、JSON5、SQL 字符串值的文本用不同表示转换只在确实需要时才发生。2.4 合法的 JSONB BLOB一个合法的 JSONB BLOB 由单个 JSON 元素构成且该元素必须恰好填满整个 BLOB。这个外层元素通常是对象或数组其 payload 内含更多元素但也可以是字符串、数字、布尔或 null 等基本值。内置 JSON 函数在判断一个 BLOB 参数究竟是 JSONB 还是普通 BLOB 时会检查外层元素的头是否格式良好且该元素是否完整填满 BLOB。两个条件都满足BLOB 才被接受为 JSONB 值。源码 json.c 中的jsonArgIsJsonb函数正是这一判断的实现它先要求首字节低 4 位类型 ≤JSONB_OBJECT再通过jsonbPayloadSize解析出头部大小与 payload 大小验证sz n nBlob元素恰好填满 BLOB并校验 null/true/false 类型的 payload 大小必须为 0。若 BLOB 不是合法 JSONBSQLite 会退化为把 BLOB 当文本再按 JSON 解析的兼容行为见 json.c 中jsonParseFuncArg的注释tag-20240123-a这也是历史兼容性约束的结果。3. 在 libSQL 中使用 JSONB3.1 jsonb_* SQL 函数族libSQL 内置了一整套以jsonb_为前缀的函数输出即为 JSONB BLOB同时普通json_*函数也接受JSONB 作为输入。函数注册表见 json.c 的sqlite3RegisterJsonFunctionsJSONB 输出函数对应文本 JSON 函数作用jsonb(X)json(X)校验并规范化 JSON输出 JSONB BLOBjsonb_array(...)json_array(...)构造 JSONB 数组jsonb_object(...)json_object(...)构造 JSONB 对象jsonb_extract(X,P1,...)json_extract(X,P1,...)按路径提取jsonb_insert(X,P,V,...)json_insert(X,P,V,...)插入不覆盖已有jsonb_set(X,P,V,...)json_set(X,P,V,...)设置覆盖已有jsonb_replace(X,P,V,...)json_replace(X,P,V,...)替换jsonb_remove(X,P,...)json_remove(X,P,...)按路径删除jsonb_patch(X,Y)json_patch(X,Y)应用补丁合并jsonb_group_array(X)json_group_array(X)聚合为数组返回 JSONBjsonb_group_object(N,V)json_group_object(N,V)聚合为对象返回 JSONB从注册表可看到jsonb_*变体在内部复用了与json_*相同的实现函数如jsonb_extract与json_extract都指向jsonExtractFunc差别仅在于返回值标记为 JSON BLOB 子类型。由于 JSONB 本身就是 BLOB应用中可以将它直接存入BLOB列甚至声明为JSON BLOB列。3.2 测试用例验证测试套件 jsonb01.test 直接验证了 JSONB 的读写闭环先CREATE TABLE t1(x JSON BLOB)并以jsonb({a:5,b:{x:10,y:11},c:[1,2,3,4]})写入 JSON5 风格文本未加引号的键然后对jsonb_remove(x, $...)与json_remove(x, $...)做等价性断言覆盖$.a、$.b.x、$.c[0]、$.c[#]、$.c[#-2]等路径删除场景同时验证非法字节序列如x8ce6ffffffff171333会被拒绝并报malformed JSON。这证明 JSONB 与文本 JSON 在函数语义上完全等价且非法 BLOB 能被严格校验。更多 JSONB 相关用例还分布在 json101.test、json102.test、json501.test、json502.test 以及 json/json-speed-check.sh性能对比脚本中。4. 小结JSONB 是 SQLite 3.45.0 起引入的 JSON 二进制编码以 BLOB 存储通常比文本 JSON 小 5%10%解析 CPU 开销不足一半每个元素 19 字节头 payload头的高 4 位编码大小档位低 4 位编码元素类型实际值域 012数值以 ASCII 文本存储字符串按来源细分四种类型转换采取懒策略使 JSONB 能直接充当 JSON 函数的内部解析树JSONB 是内部格式应用应只通过jsonb_*或json_*函数访问不应直接解析字节但格式向后兼容、跨版本稳定其完整规范记录在 jsonb.mdlibSQL 完整继承了该能力实现集中在 json.c并被 jsonb01.test 等测试覆盖。【免费下载链接】libsqllibSQL is a fork of SQLite that is both Open Source, and Open Contributions.项目地址: https://gitcode.com/GitHub_Trending/li/libsql创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考