跳到主要内容

JSON 类型

PostgreSQL 提供 json 和 jsonb 两种 JSON 类型。两者都校验 JSON 语法,但 json 保留输入文本的空白和键顺序,jsonb 以分解后的二进制形式存储,支持更高效的查询和索引。业务查询通常优先选择 jsonb。

json 与 jsonb​

特性jsonjsonb
存储原始文本分解后的二进制结构
保留空白和键顺序是否
重复键输入可保留,处理时取最后值不保留重复键
查询和索引能力有限支持运算符和 GIN
写入成本通常较低需要解析和转换

若必须逐字保留签名、格式化或键顺序,使用 json 或额外保存原文;否则 jsonb 更适合应用数据。

创建和访问​

CREATE TABLE profiles (
profile_id bigint PRIMARY KEY,
attributes jsonb NOT NULL DEFAULT '{}'::jsonb
);

SELECT attributes->>'nickname' AS nickname,
attributes->'preferences' AS preferences
FROM profiles;

-> 返回 JSON,->> 返回文本。路径不存在时通常得到 NULL,这与键存在但值为 JSON null 的情况需要业务区分。

路径、包含和修改​

SELECT attributes #>> '{address,city}' FROM profiles;
SELECT attributes @> '{"role":"admin"}'::jsonb FROM profiles;

UPDATE profiles
SET attributes = jsonb_set(attributes, '{preferences,theme}', '"dark"')
WHERE profile_id = $1;

@> 是包含判断,数组和对象的语义需通过测试确认。jsonb_set、||、- 等操作符适合小规模修改;高频更新大型 JSON 文档会产生整行版本和 WAL,可能不如拆列或拆表。

索引​

CREATE INDEX profiles_attributes_gin_idx
ON profiles USING gin (attributes);

CREATE INDEX profiles_role_idx
ON profiles ((attributes->>'role'));

通用 GIN 适合 @> 等包含查询;jsonb_path_ops 索引更紧凑但支持的运算符集合较少。对固定路径的等值、排序或范围过滤,表达式 B-tree 往往更合适。

约束与数据建模​

JSON 类型只保证整体是合法 JSON,不会自动保证字段存在、类型正确或值在枚举集合内。可用 CHECK 做轻量约束:

ALTER TABLE profiles
ADD CONSTRAINT profiles_role_check
CHECK (attributes->>'role' IN ('user', 'admin'));

稳定、经常关联、需要外键或聚合的字段应使用普通列;变化频繁、结构不稳定且主要按文档读取的字段才适合放入 JSONB。混合模型通常比“全部 JSON”更容易维护。

数字、日期和 NULL​

JSON 数字进入数据库后仍可能遇到精度和范围问题;金额不要只依赖 JSON 数字,必要时抽取到 numeric。JSON null、SQL NULL 和缺失键不是一回事,接口契约应明确三者的区别。

常见误区​

  • 把 json 和 jsonb 当作完全等价,忽略键顺序和重复键差异。
  • 只建通用 GIN 索引,却没有针对高频路径建立表达式索引。
  • 把所有表字段塞进 JSON,导致外键、约束和统计困难。
  • 用字符串拼接构造 JSON,造成转义错误或注入风险;应使用参数化对象。
  • 以为 JSONB 查询会自动验证业务 schema。