超越jsonb:postgres的通用非结构化数据类型

2020-08-18 07:26:01

不可否认,jsonb是国王。它是一种非常灵活的数据类型,允许非结构化/无模式存储。它有非常强大的索引机制,其内部表示相当紧凑和高效。它附带了高级运算符和表达式来查询/提取其中的一部分,最近还添加了SQL/JSON路径功能来扩展以符合SQL标准。

有无数的帖子和资源解释了这种数据类型的优点以及使用它可以实现的功能。没有什么要补充的,除了重申我对所有那些为它的创造和贡献而工作的人的感激之情。

但有个问题。JSONB支持…。就是JSON!太棒了,但是够了吗?

JSON是一种容器数据类型,这意味着它可以存储中包含的其他数据类型。它支持哪些数据类型?从JSON规范来看,它支持以下内容:

数组,或者更好地称为“包”或“容器”,可能是混合类型的元素序列。

对象,它是键-值对的集合,其中的值可以是任何其他JSON数据类型。

Jsonb在内部将这些JSON数据类型映射到Postgres类型。很容易看到已解析(JSON)数据类型:

从jsonb_each(';{";a";:42,";b";:true,";c";:";hi";,";d";:NULL,";e";:[42,";hi";],";f";:{&#。:1}}';);┌─┬─┬─┐│Key│Value│jsonb_Typeof│├─┼─┼─┤│a│42│Number││b│True│boolean││c│";hi";│String││d│NULL│NULL││e│[42,";hi";]│数组││f│{";a";:1}│Object│└─┴─┴─┘

标量类型映射为布尔、文本和数字。从本质上讲,有三种不同的数据类型。现在检查Postgres中可用的所有数据类型。有几十个。很明显,你可以用你自己的来扩展。本质上,您需要将任何现有数据类型合并为数字或文本,这不是有点限制吗?如果您想表示时间戳,该怎么办?将其转换为文本(Postgres会为您做这件事,但您明白这一点)。要不要一只篮子,或者一个点子?我的自定义令人惊叹的数据类型怎么办?

根据JSON规范,这是正确的,没有其他事情可做。所以问题不在于jsonb是否有任何实现缺陷,而在于postgres用户是否对“只使用JSON”就足够了。

我发现必须在JSON中存储二进制数据(ByteA)特别令人沮丧。由于JSON中没有byteA数据类型(因此,jsonb也没有),您需要将其转换为文本表示形式。有几种解决方案,但没有一种足够好:

将其逐个字节转换为字符串。这可能是一个非常糟糕的选择,因为它面临两个主要问题:一个是postgres字符串不符合严格的UTF-8(这是另一篇博客文章…的主题)。,无法存储UTF-8空(\0)字符。因此,如果这些字节中有一个字节的值(很可能)为0,则无法存储该字节。其次,解释为字符串的字节序列与原始字节序列的顺序不同。

以Base64编码。除了额外的空格之外,不会保留排序(即,原始字节序列上的索引将产生与base64编码文本上的索引不同的顺序)。如果需要保留顺序,则解决方案是对解码值使用表达式索引。这无论如何都会增加开销。

用base32hex编码,这是一种更冗长的编码,但它保持了顺序。Postgres中没有用于此编码的函数。

那么,您将如何使用当前的JSON和jsonb存储其他特定的、更紧凑的或从其他数据类型的现有函数中获益呢?避免数据类型信息擦除的唯一选择是将数据类型编码为JSON字符串的一部分。类似于:

很容易看出这种方法的几个缺点:更多的冗长和存储需求;需要连续转换;需要根据数据类型进行条件转换。

我并不是唯一一个追求更通用、支持更多类似JSON数据类型的语言的人,比如:

DynamoDB的项HTTPAPI。这是一个非常有趣的情况,因为它是一个符合JSON的输入/输出接口,类型定义遵循与上面概述的方法类似的方法(其中值是一个同时包括类型和值本身的对象),但声称以优化形式(而不是按原样)存储在内部。它有有趣的复合类型,如“同源数组”,其中元素必须是同一时间或集合,这禁止重复值。

那么,这种通用的非结构化数据类型在Postgres中是如何工作的呢?我想说它可以利用所有现有的jsonb基础设施。毕竟,jsonb已经知道每个元素的数据类型,因此只需要扩展Tomany,潜在的任意数据类型(也支持自定义数据类型)。因此,新的数据类型应该很容易添加。其他特征(如确保支持集合的数组元素之间的均匀性;或确保元素内的唯一性)可能需要一些区别。作为一个超集,我希望相同的代码库可以很好地适应这两种数据类型(过去也曾从hstore进行过类似的改造,以使用jsonb的代码库)。

其中一个主要要求是,这个通用的非结构化数据类型将是实际JSON的超集,因此任何JSON字符串(以及随后的json和jsonb值)都可以隐式转换为它。我设想最困难的部分,或者至少是那些可能引发更多讨论的部分,将是:

是否输入语法?一些想法:根据数据类型使用不同的值括起来。例如密钥:{{dmFsdWU=}}(Amazon Ion)。

将值转换为具有type(文档的键)和value的嵌套JSON对象,类似于DynamoDB的HTTP API:";key";:{";N";:";42";}。

那你怎么想?Postgres是否应该获得JSON超集数据类型,这是一种通用的非结构化数据类型?留下您的评论和/或将您的回复发到@ongresinc开始对话。

个人注释:我发现很难理解为什么Oleg Bartunov,在Postgres的JSON中最重要的贡献者之一,最近被从“主要贡献者”降级为“贡献者”,不仅我认为主要贡献是不能收回的,而且在这个特殊的情况下,感觉更难理解。让这成为我的公开呼吁,要求扭转这种情况,并为Postgres二十年的贡献提供适当的公众认可。↩︎