何时使用 JSON 类型
JSON 类型适用于对结构动态或不可预测的 JSON 对象中的特定字段进行查询、过滤和聚合。它通过将 JSON 对象拆分为独立的子列来实现这一点。与 Map 或将字符串解析后再处理等替代方案相比,这种方式能显著减少读取的数据量,并加快针对所选字段的查询。
不过,这也伴随着一些重要的权衡:
INSERT更慢 - 将 JSON 拆分为子列、执行类型推断以及管理灵活的存储结构,会使插入速度比将 JSON 存储为简单的String列更慢。- 读取整个对象时更慢 - 如果你需要获取完整的 JSON 文档 (而不是特定字段) ,
JSON类型会比从String列中读取更慢。当你不进行字段级查询时,从独立子列重建对象所带来的额外开销并无收益。 - 存储开销 - 与将 JSON 存储为单个字符串值相比,维护独立子列会增加额外的结构开销。
在以下情况下使用 JSON 类型:
- 你的数据具有动态或不可预测的结构,不同文档中的键各不相同
- 字段类型或 schema 会随时间变化,或因记录而异
- 你需要对 JSON 对象中特定路径上的数据进行查询、过滤或聚合,但其结构无法预先确定
- 你的使用场景涉及半结构化数据,例如日志、事件或用户生成内容,且其 schema 不一致
在以下情况下使用 String 列 (或结构化类型) :
- 你的数据结构已知且一致——在这种情况下,应改用普通列、
Tuple、Array、Dynamic或Variant类型 JSON文档被视为不透明 blob,只会被完整存储和取回,而不会进行字段级分析- 你不需要在数据库中对单个
JSON字段进行查询或过滤 JSON只是传输/存储格式,不会在 ClickHouse 内部进行分析
使用 JSON 的注意事项与技巧
- 使用列定义中的提示指定路径类型,为已知子列预先指定类型,避免不必要的类型推断。
- 如果不需要某些值,可使用 SKIP 和 SKIP REGEXP跳过路径,以减少存储占用并提升性能。
- 避免将
max_dynamic_paths设置得过高——数值过大会增加资源消耗并降低效率。经验上,建议将其控制在 10,000 以下。
类型提示类型提示不仅能避免不必要的类型推断——还可以彻底消除存储和处理过程中的间接层。带有类型提示的 JSON 路径会始终像传统列一样存储,无需依赖 判别列 或在查询时进行动态分辨率。这意味着,只要类型提示定义得当,嵌套 JSON 字段就能获得与一开始就建模为顶层字段时相同的性能和效率。因此,对于那些整体上较为一致、但仍希望保留 JSON 灵活性的数据集,类型提示提供了一种便捷方式,让你无需重构 schema 或摄取管道,也能保持性能。
高级功能
- JSON 列可像其他列一样用作主键。不能为子列指定编解码器。
- 它们支持通过
JSONAllPathsWithTypes()andJSONDynamicPaths()等函数进行内部信息查看。 - 你可以使用
.^语法读取嵌套的子对象。 - 查询语法可能与标准 SQL 不同,对嵌套字段可能需要特殊的类型转换或运算符。
示例
tags 的列。如果它只是一个字符串列表,我们可以将其建模为 Array(String);但这里假设你可以添加包含混合类型的任意标签结构 (注意,score 既可以是字符串,也可以是整数) 。修改后的 JSON 文档如下:
tags 列。下面给出这两种示例:
我们在 JSON 定义中为
update_date 列提供了类型提示,因为会在排序键/主键中用到它。这有助于 ClickHouse 知道该列不会为 NULL,并确保它知道应使用哪个 update_date 子列 (每种类型都可能有多个,否则这里会存在歧义) 。JSONAllPathsWithTypes 函数和 PrettyJSONEachRow 输出格式查看随后推断出的 schema:
tags 列来对此建模。通常更推荐这种方式,因为它能将 ClickHouse 所需的推断降到最低:
tags 的类型。