半结构化数据(JSONB)

半结构化数据

关系型与文档型的完美结合

当我们考虑数据存储,纯关系型和纯文档型数据库各有其优缺点。想象一下酒店预订系统的场景:

关系型数据库 :在处理客户信息和预订详情时效果卓越。但面对频繁变动或半结构化的客户要求,例如特殊的房间配置或额外的服务要求,其结构就显得过于僵化。

文档型数据库 :对于上述的半结构化数据,它的灵活性无与伦比。但当多名客户预订同一房间时,房间的信息在每个客户文档中都会重复,导致数据冗余和更新难度增加。

如果只选其一,似乎都不是理想的选择。但 ProtonBase 通过引入 JSONB 数据类型,实现了关系型和文档型的完美结合。在一方面,我们可以轻松管理关系型数据,如客户和预订信息;另一方面,仍能为半结构化的特殊要求提供灵活的支持,而无需担心数据重复。

所以,单独使用关系型或文档型数据库都有其局限性。而 ProtonBase 正是通过结合它们的优势,为用户带来了最佳的数据存储解决方案。

何时使用 JSONB

高效处理半结构化和层次化数据 :JSONB 特别适合存储和查询半结构化数据,如用户设置、配置选项、多级分类系统,或任何形式的嵌套数据结构。它的灵活性在于可以直接在数据库中存储这些复杂的数据结构,而无需将其分解为多个关系表。这样可以简化查询并提高性能,尤其是在处理大型或复杂的层次数据时。

适应动态或不定型数据模型 :对于具有频繁更改或不确定属性的数据模型,JSONB 是理想选择。它允许在不修改现有数据库结构的情况下存储新的或不常见的属性。这种灵活性对于快速迭代和适应不断变化的数据需求至关重要,同时也减少了因数据模型更改而导致的维护成本和复杂性。

简化与外部系统的集成 :当与第三方系统集成时,常常需要处理各种格式的数据。JSONB 提供了一种高效的方法来存储这些不同格式的数据,同时保留其原始结构和内容。这使得将数据导入和导出到不同系统变得更加容易,同时也方便了数据的后续处理和分析。

JSONB 与其他数据类型的关系

灵活性与严格性 :传统的数据类型(如整型、字符型等)以其严格性著称,这对于保持数据的一致性和完整性至关重要。相比之下,JSONB 的优势在于其灵活性,允许存储和处理不规则或非结构化的数据。选择合适的数据类型取决于应用程序的具体需求:对于需要强数据类型的场景,传统数据类型可能更为合适;而在需要处理多变或不规则数据的情境中,JSONB 则展现出其优势。

性能 :虽然 JSONB 的灵活性在处理非结构化和变化多端的数据方面无疑提供了显著优势,但在某些特定的查询操作上,它的表现可能会略逊于传统的数据类型如整型或字符型。这种差异主要体现在处理高频率查询的场景中,其中传统数据类型可能展现出更优化的查询效率。因此,在设计数据库架构时,建议仔细考虑各个字段的使用模式和查询需求,从而在灵活性和性能之间找到最佳平衡点,确保数据库系统的整体性能符合预期目标。

存储效率的权衡 :JSONB 在存储更复杂的数据结构和支持复杂查询操作方面具有优势,但这往往以牺牲存储空间为代价。JSONB 数据类型通常占用更多的存储空间,这在资源有限的环境中可能成为一个考虑因素。因此,在选择使用 JSONB 时,需要平衡其灵活性和存储效率的关系,根据实际应用场景和资源限制做出合理决策。

总之,选择 JSONB 还是其他传统数据类型取决于具体的应用需求。如果您的应用需要高度的数据结构灵活性和迭代速度,JSONB 可能是一个很好的选择。但在决策时,也要考虑查询性能和存储的需求。

使用方式

创建和管理表

创建含有 JSONB 列的表

CREATE TABLE json_t
(
    id          serial PRIMARY KEY,
    json_column jsonb
);

插入 JSONB 数据

INSERT INTO json_t(json_column) VALUES ('{"key": "value"}');

读取 JSONB 数据

SELECT json_column -> 'key' FROM json_t;

更新 JSONB 数据

UPDATE json_t SET json_column = jsonb_set(json_column, '{key}', '"new_value"');

删除 JSONB 内部的某些数据

UPDATE your_table SET json_column = json_column - 'key';

查询操作

使用 ->->> 运算符

  • ->: 返回 JSONB 数组元素或对象字段。
SELECT json_column -> 'key' FROM json_t;
SELECT json_column -> 0 FROM json_t; -- 获取数组的第一个元素
  • ->>: 返回 JSONB 数组元素或对象字段作为文本。
SELECT json_column ->> 'key' FROM json_t;

使用 @><@ 判断是否包含

  • 判断 JSONB 列是否包含指定的键/值或元素。
SELECT * FROM json_t WHERE json_column @> '{"key": "value"}';
SELECT * FROM json_t WHERE json_column <@ '{"key": "value", "another_key": "another_value"}'::jsonb;

使用 #>#>> 获取嵌套数据

SELECT json_column #> '{path, to, key}' FROM json_t;
SELECT json_column #>> '{path, to, key}' FROM json_t;

使用 ??| 运算符判断键/值的存在性

  • 判断 JSONB 列是否含有特定的键或者值。
SELECT * FROM json_t WHERE json_column ? 'key';
SELECT * FROM json_t WHERE json_column ?| array['key1', 'key2'];

函数

  • jsonb_set: 更新或插入新值。
SELECT jsonb_set(json_column, '{key}', '"new_value"') FROM json_t;
  • jsonb_insert: 在指定路径插入新值。
SELECT jsonb_insert(json_column, '{path, to, key}', '"new_value"') FROM json_t;
  • jsonb_array_elements: 该函数用于将 jsonb 数组展开为多行。这个函数返回的是一个集合,每一行对应于原始 jsonb 数组中的一个元素。要获取结果,通常需要使用 FROMJOIN 与此函数一起。

例如,考虑一个 json_column 包含以下数据:

["element1", "element2", "element3"]

使用 jsonb_array_elements 如下:

SELECT jsonb_array_elements(json_column)
FROM json_t;

索引与性能

为何和何时需要为 JSONB 数据建索引

  • 当频繁查询 JSONB 数据且数据量大时,建立索引可以显著提高查询速度。

创建 GIN 索引

  • GIN (Generalized Inverted Index) 索引特别适合于 JSONB 数据,因为它可以有效地处理复杂的嵌套数据和搜索查询。
CREATE INDEX json_t_jsonbcol_gin ON json_t USING gin(json_column);;

查询使用 GIN 索引

  • GIN 索引可以加速许多类型的查询,例如使用 @> 查找包含某个键/值的记录:
SELECT * FROM json_t WHERE json_column @> '{"key": "value"}'::jsonb;

删除索引

  • 如果某个时候你不再需要这个索引,可以使用以下命令删除它:
DROP INDEX json_t_jsonbcol_gin;

使用与建议

  • 创建适当的索引对于大数据集和频繁的查询非常关键,它可以显著提高查询性能。但是,请注意,索引也会增加数据库的存储需求,并可能略微增加插入、更新或删除记录的时间,因为索引也需要被更新。因此,在决定创建索引之前,权衡好利弊是很重要的。

高级操作

JSONB 路径查询

  • 使用 JSON 路径表达式可以查询嵌套的数据。
SELECT json_column #> '{path, to, nested, key}' FROM json_t;

聚合 JSONB 数据

  • 可以使用聚合函数将多行 jsonb 数据聚合到一个数组或对象中。
-- 聚合为数组
SELECT jsonb_agg(json_column) FROM json_t GROUP BY some_column;
 
-- 聚合为对象(将jsonb合并为一个对象)
SELECT jsonb_object_agg(key_column, value_column) FROM json_t;

修改嵌套数据

  • 使用 jsonb_set 可以在指定路径修改嵌套数据。
UPDATE json_t SET json_column = jsonb_set(json_column, '{path, to, nested, key}', '"new_value"');

数组操作

  • 可以使用特定的函数和操作符来操作 jsonb 数组。
-- 在数组的末尾添加元素
UPDATE json_t SET json_column = json_column || '["new_element"]'::jsonb;
 
-- 获取数组长度
SELECT jsonb_array_length(json_column) FROM json_t;

删除多个键

  • 除了单个键,jsonb_strip_nulls- 操作符还可以删除多个键。
UPDATE json_t SET json_column = json_column - '{key1, key2}';

查询特定的键/值

  • 使用 ?, ?&, ?| 操作符可以查询特定的键或值。
-- 检查是否存在某个键
SELECT * FROM json_t WHERE json_column ? 'some_key';
 
-- 检查是否存在多个键
SELECT * FROM json_t WHERE json_column ?& array['key1', 'key2'];
 
-- 检查是否存在其中任一键
SELECT * FROM json_t WHERE json_column ?| array['key1', 'key2'];

最佳实践

选择合适的数据格式

  • 尽管 jsonb 为数据提供了灵活性,但并非所有情况都适合使用它。对于结构固定并需要频繁进行关联查询的数据,传统的关系型数据库表结构可能更合适。

使用索引

  • 对于大数据集和频繁的查询,适当的索引是关键。GIN 索引通常对 jsonb 数据查询最有效。

注意查询性能

  • 避免不必要的嵌套查询,并尽可能使用索引进行查询,特别是在大数据集上。
  • 使用 EXPLAIN 来分析查询计划,以确定是否使用了索引和是否可以进一步优化。

数据规范化

  • 尽管 jsonb 提供了存储非规范化数据的灵活性,但过度使用可能导致数据冗余和查询性能问题。确保数据组织得当,并根据需要进行规范化。

使用适当的函数和操作符

  • ProtonBase 提供了许多专门用于操作 jsonb 数据的函数和操作符。了解这些工具并根据需要使用它们。

避免频繁修改

  • 频繁修改 jsonb 列的数据可能导致性能下降,因为这通常涉及重新写入整个 jsonb 对象。

存储大小

  • 请注意,jsonb 数据的存储大小通常比等效的关系数据大。在考虑存储大量数据时,请进行适当的容量规划。

考虑备份策略

  • 由于 jsonb 数据可能比传统数据更复杂和更大,因此在计划备份策略时要特别小心。

常见问题与解决方案

数据插入错误

问题:当试图插入非有效的 JSON 数据时出现错误。

解决方案:

  • 确保您插入的数据是有效的 JSON 格式。
  • 使用在线工具或软件库验证 JSON 数据。
  • 如果遇到错误提示,首先检查 JSON 格式。

查询没有返回预期结果

问题:当查询特定的 jsonb 键或值时,没有返回预期的结果。

解决方案:

  • 确保查询的键或值与存储在 jsonb 列中的数据完全匹配,包括大小写。
  • 使用 jsonb_pretty 函数来格式化输出,这有助于识别和解决数据中的问题。