PostgreSQL 实践:JSON vs JSONB

—— 为什么 99% 的场景使用 JSONB?

摘要: 随着 PostgreSQL 成为新一代“全能型数据库”,JSON 支持也让它具备了替代 MongoDB 的能力。但在建表时,你是否也纠结过——字段到底该用 json,还是 jsonb?两者虽然都能存 JSON,但底层结构、性能表现、索引能力却完全不同。 选错类型,查询性能可能慢几十倍。 本文将从底层机制、性能、索引、应用场景和 Spring Boot 实战全面解析两者的差异,并给出最稳的选型建议。


01|为什么 PostgreSQL 有两种 JSON?

不同于 MySQL,PostgreSQL 提供:

  • ** json**:文本 JSON
  • ** jsonb**:二进制 JSON(推荐)

它们都能存储合法 JSON,但核心目标完全不同。

一句话总结:

  • json:原样存储,格式完全保留
  • jsonb:性能与索引优先,结构化存储

02|JSON vs JSONB:底层差异

🔹 JSON:只是“被验证过的字符串”

特点:

  • 按文本原样存储(空格、换行、缩进都会保留)
  • 保留键顺序、保留重复键
  • 读取时必须重新解析 → 查询性能很差

适合:只保存不查询的原始报文、审计日志。


🔹 JSONB:结构化的二进制存储(性能王者)

特点:

  • 转为二进制结构后存储(紧凑占用更小)
  • 自动去重键(只保留最后一个)
  • 键顺序会被重新排序
  • 支持索引(GIN)
  • 查询无需解析 → 非常快

查询性能可比 JSON 快数十倍。


03|一张表看懂差别

维度JSONJSONB(推荐)
写入速度稍慢
读取速度❌ 慢✔ 非常快
查询能力✔ 强
是否支持 GIN 索引❌ 否✔ 是
空间占用
支持局部更新较弱✔ 强大(jsonb_set)
保留格式✔ 完整保留❌ 不保留

如果你需要查询 JSON 内容 → 必须使用 JSONB。


04|什么时候应该用 JSON?(非常少)

必须同时满足:

  1. 你不会查询 JSON 内部字段;
  2. 你需要保留原始格式(顺序、空格、换行);
  3. 你非常在意写入速度;

典型场景:

  • 原始日志存档
  • 审计数据快照
  • 协议报文的字节级保留

05|什么时候应该用 JSONB?(>99% 的业务)

出现以下任意一点 → 选 JSONB:

  • 需要查询 JSON 内部字段
  • 需要索引内部字段
  • JSON 内容会变化
  • 需要局部更新某个字段
  • 典型半结构化数据

常见业务:

  • 即时通讯消息体(读取状态、上传状态等)
  • 电商商品属性(颜色、规格等)
  • SaaS 动态设置(用户配置项)
  • IoT 设备上报数据

06|实战:PostgreSQL 表与索引

🔹 建表推荐:

CREATE TABLE wx_message (
    id BIGINT PRIMARY KEY,
    payload JSONB
);

🔹 GIN 索引(性能飞升关键)

CREATE INDEX idx_wx_message_payload 
ON wx_message 
USING GIN (payload);

没有 GIN 索引,JSONB 实力只发挥一半。


07|Spring Boot / Hibernate 最佳实践

🔹 Entity 映射

@JdbcTypeCode(SqlTypes.JSON)
@Column(columnDefinition = "jsonb")
private String payload;

为什么用 String

  • 避免复杂的 JSON 多态反序列化
  • 保证结构灵活
  • 与前端自由 JSON 协议更匹配

🔹 JSONB 局部更新(非常关键)

例如更新消息的 mediaStatus

UPDATE wx_message
SET payload = jsonb_set(payload, '{mediaStatus}', to_jsonb('uploaded'), true)
WHERE id = 123;

优点:

  • 原子更新,不需要查出来再写回
  • 不会发生并发覆盖
  • 比读取->反序列化->再保存快得多

Spring JPA 版本:

@Modifying
@Transactional
@Query("""
    UPDATE wx_message
    SET payload = jsonb_set(payload, '{mediaStatus}', to_jsonb(:status), true)
    WHERE session_id = :sessionId AND wxid = :messageId
"
"")
int updateMediaStatus(Long sessionId, String messageId, String status);

08|总结:选型建议

✔ 默认选 JSONB

  • 查询快
  • 索引强
  • 占用小
  • 支持局部更新
  • 满足绝大多数实际业务需求

❗仅在以下场景用 JSON:

  • 必须保留原始格式(日志、协议原文)
  • JSON 不参与任何查询

本文由 mdnice 多平台发布

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值