使用 JSON 管理复杂数据

Rob Giseburt 五月 24, 2023

您是否正在查看陈旧、平铺的关系数据库并思考:“它是如此僵化和毫无生气!” 你需要多态性吗?您是否有一个没有明显结构的 NoSQL 数据库,给您带来了缺乏关系的痛苦?使用 PostgreSQL 中的 JSON,您既可以使用可变的结构,也能获得数据的关系!

scenery

我们要解决什么问题?

我想解释一下我们在这里研究的几十年前的问题。

让我们看一个相当常见的用例:用于在线出版物的内容管理系统(CMS)。该出版物包含文章或故事,其中包含对文案、图像、视频和广告的混合引用。人们也参与故事 - 一些人参与故事的制作(作者,编辑,摄影师等),一些人在故事中被引用。添加地点、事物、事件、产品和其他您甚至还不知道的引用,它会变得非常复杂。

其中每个实体都有不同但重叠的内容类型、元数据类型和引用。本质上,每个实体都有自己的架构。更糟糕的是,随着时间的推移,您可能会发现并添加新实体——例如拍摄脚本、图片版权、嵌入的广告文案等。

最后一根稻草是,您承担了一项不可能完成的任务,即向数据库添加更改日志,以跟踪某些实体的某些更改,保留旧数据和新数据,并将其合并发送到一个统一的信息流以通知下游系统。

这篇文章将解释如何构建这样一个野兽,我们将使用 PostgreSQL 强大的关系和 JSON 功能组合来做到这一点!

技术选择

在构建处理复杂数据的系统时,技术决策归结为在灵活性、性能和可维护性之间找到适当的平衡。在本文中,我们将重点介绍存储所有这些背后的数据的数据库。

关系数据库是刚性数据架构的典型代表。对于添加到数据集的每个自由度,性能、存储使用和可维护性方面的成本呈指数级增长。

在关系数据库中表示复杂的可扩展数据是一个由来已久的问题,并且已经使用了许多解决方案:

  • 添加表 – 为每个新实体添加一个或多个新表。这会很麻烦,并且通常会导致频繁的数据库架构升级。
  • 添加列/参数 – 针对特定情况向表添加其他列。这使得表行成为不同类型实体的所有可能属性的联合。数据分布很薄,必须知道行所代表的实体类型。这会将数据完整性和其他约束的责任推给应用程序。这些保证必须通过向数据库写入代码来提供,或者在从数据库读取的任何内容中得到补偿,或者两者兼而有之。
  • 列重载 – 提供根据上下文对值进行不同解释的列。例如,使用文本或二进制列并存储可能是数字、布尔值、文本甚至深层数据结构(如 JSON)的数据。这是一个糟糕的想法,但人们会这样做。这还需要深入了解数据集,并且没有数据形状或引用完整性保证。
  • 外部数据或不透明数据 – 灵活的数据存储在完全不同的系统中,例如文件或其他数据库系统,或者可能作为数据库中的不透明“blob”。这可能会对性能产生影响,并且还缺乏数据质量保证。
  • 多对多关系 – 是关系数据库的另一个症结所在。我们稍后会回到这个问题。

NoSQL 数据库解决了其中一些问题 - 但不是全部:

  • NoSQL 数据库通常基于 JSON 文档,具有多种查询机制之一。大多数 NoSQL 数据库都是无模式的,没有关系的概念。这些通常相当于一个包含部分索引的 JSON 文档的文件夹,具有类似 REST 的接口。
  • 虽然 NoSQL 数据库有其用途,但关系和数据完整性由使用代码决定,并且通常最终与索引和缓存服务相结合,数据形状完整性留给使用数据库的代码。

看看 PostgreSQL: 为什么是 JSON?

JSONJavaScript Object Notation)已经远离了 JavaScript 的根源,现在支持作为简单而深入的结构化数据的实际机器对机器数据交换格式。JSON 数据是使用键值映射(又名“对象”)和数组的树结构,叶节点是数字、字符串、布尔值或空值。

JSON 也是一种基于文本的格式,需要解析才能有用,并且没有遵循架构的内置机制。但是,JSON 很容易被大多数编程语言解析,包括 Web 浏览器中的 JavaScript,并且在 REST 和 GraphQL API 中大量使用。

大多数 NoSQL 数据库使用 JSON 的灵活性和表达能力。但是,JSON 文档本身是一个平面的二维树,与其他文档没有任何链接。如果没有结构强制实施,管理大量此类数据很快就会成为负担,留给使用数据库的代码(或人员)。

相比之下,非常严格的行列关系数据库表可以通过引用成为高维数据。由于列中的数据传统上是纯标量,因此它实际上是所有叶子的树。关系数据库的刚性提供了智能索引、存储、约束,提供了数据和引用的结构完整性、唯一性保证以及防止丢失基本数据。

现在我们可以将两者结合起来,以获得两者的最佳部分!

PostgreSQL 对 JSON 的支持

PostgreSQL 在 9.2 版中添加了 JSON 支持,并极大地扩展了此功能。AWS Aurora Serverless(PostgreSQL 风格)使用 PostgreSQL 10 协议和语言。不幸的是,这不包括 PostgreSQL 12 中较新的 SQL / JSON路径,因此请相应地进行计划。没有它,SQL / JSON 路径无法完成任何工作,但它更方便。

什么是 JSONb?

为了提高使用和空间的效率,应该使用jsonb(JSON 二进制)列,而不是存储字符串并在使用时在json列中解析它们。引用文档

JSON 数据类型存储输入文本的精确副本,处理函数必须在每次执行时重新解析;而 JSONb 数据以分解的二进制格式存储,由于增加了转换开销,输入速度略慢,但由于不需要重新解析,因此处理速度明显更快。JSONb 还支持索引,这可能是一个显着的优势。

由于额外的索引,JSONb 还具有完成下面描述的任务所需的运算符,而json列则没有。在我说“JSON 数据”的每种情况下,我指的是存储在jsonb列类型中的 JSON 数据。

多对多关系

在数据库列中使用 JSON 最强大的功能之一是能够一次表达多对多关系,以及有关这些关系的任何其他数据。多对多关系的选项包括:按多到少的顺序:

  • 联接表 – 在经典关系数据库中,通常使用关联表,或更正式的“关联实体”。这是一个包含主键引用列的表,用于联接两个或多个其他表。有关关系的其他元数据也可以保存在其他列中。
    • 这些索引很好,支持约束检查,并且是众所周知的设计模式。但它们可能需要很多维护工作。
    • 由于数据存储在多个表中,因此需要多个语句来冻结或解冻具有关系的数据集。
  • 数组列 – 允许单个列包含相同类型值的可变长度列表。这些可以模拟关联表,但联接是不对称的,因为从数组列联接比联接到数组列更容易。数组列在 PostgreSQL 中受支持,但在许多其他数据库中不受支持。
    • 数组索引使用与jsonb列相同的 GIN 索引类型,并且不能像使用 JSON 那样包含每个值的其他元数据。(注意:可以创建jsonb[]列,但我们不建议这样做,因为包含数组的jsonb列没有值。
  • JSON – 允许表示完整的关系树,以及有关这些关系的相关元数据。这是我们在这篇文章中使用的模式。
    • 这不如使用关联表进行连接那样高效,主要是由于索引的差异,但这允许在单个冻结或解冻语句中用元数据表示详细的关系。
    • 与数组类似,联接性能是不对称的。
  • 图数据库 – 将关系表示为特定的“第一类实体”。在 PostgreSQL 支持图结构特性之前,我们可以使用 JSON 来妥协。