PostgreSQL 与 MySQL 的 JSON 类型:Go 项目怎么选

关系型数据库支持 JSON 已经很成熟,但 JSON 并不意味着可以随意替代普通字段。实际项目中,更重要的问题是:PostgreSQL 的 jsonjsonb 怎么选?MySQL JSON 如何建立索引?Go 又应该怎样读写?

PostgreSQL:绝大多数情况选 jsonb

PostgreSQL 同时提供 jsonjsonb

  • json 保留输入时的文本表示,包括空格和键顺序;
  • jsonb 则会在写入时解析并转换为内部二进制格式,因此不保留原始格式和重复键。

实际项目中,PostgreSQL 默认直接选择 jsonb

原因很简单:jsonb 更适合查询、过滤和索引。例如:

SELECT * FROM events WHERE payload @> '{"user":"alice"}';

对于 JSON 内的高频查询,可以建立 GIN 索引:

CREATE INDEX idx_payload ON events USING GIN (payload);

如果只经常查询某个字段,更推荐表达式索引:

CREATE INDEX idx_user ON events ((payload ->> 'user'));

只有确实需要保留原始 JSON 文本,例如审计、签名或原样转发,才考虑 json

MySQL:JSON 只有一种,但索引方式不同

MySQL 只有 JSON 类型,内部采用二进制格式存储,可以使用 JSON 函数和路径表达式查询:

SELECT payload ->> '$.user' FROM events;

判断 JSON 内容:

SELECT * FROM events WHERE JSON_CONTAINS(payload, '"alice"', '$.user');

MySQL 不能像 PostgreSQL 那样直接对整个 JSON 列建立通用索引。对于高频字段,通常使用生成列:

ALTER TABLE events
ADD COLUMN user_name VARCHAR(64) AS (payload ->> '$.user') STORED,
ADD INDEX idx_user (user_name);

MySQL 8 还支持函数索引和多值索引,但它们都需要针对具体查询模式设计。

因此两种数据库的核心区别是:

  • PostgreSQL jsonb:适合灵活 JSON 查询,可直接使用 GIN 索引。
  • MySQL JSON:通常需要生成列或函数索引优化特定字段。

不要把 JSON 当万能字段

这是实际项目中最重要的一点。

JSON 很适合保存:

  • 用户扩展属性
  • 配置参数
  • 第三方接口原始数据
  • 字段结构不稳定的数据

但如果某个字段经常用于:

  • WHERE
  • JOIN
  • ORDER BY
  • GROUP BY

那么它通常应该成为独立列,而不是长期藏在 JSON 里。
例如订单的 status、用户 ID、创建时间等核心字段,应该正常建列。JSON 更适合作为 attributesextrametadata 一类的扩展字段。

数据库设计应该围绕查询模式,而不是围绕数据格式。

Go 应用

  • 简单场景

    // 读取
    var raw []byte
    err := db.QueryRow("SELECT payload FROM events WHERE id = $1", 1).Scan(&raw)
    
    var payload Payload
    err = json.Unmarshal(raw, &payload)
    
    // 写入
    data, _ := json.Marshal(payload)
    _, err := db.Exec(
        "INSERT INTO events (payload) VALUES ($1)", data,
    )
    

    这里需要注意:虽然 Go 驱动通常通过 []bytestring 传递 JSON,但数据库内部并不一定按文本存储。例如 PostgreSQL jsonb 和 MySQL JSON 都有自己的内部格式。

  • 复杂项目:封装泛型类型,实现 sql.Scannerdriver.Valuer

    type JSON[T any] struct {
        Val T
    }
    

    读取数据库时自动 json.Unmarshal,写入数据库时自动 json.Marshal

    这样业务代码可以直接使用:

    JSON[UserConfig]
    JSON[map[string]any]
    JSON[[]string]
    

    相比每个地方手动序列化,这种方式更统一,也更适合长期维护。

总结

实际项目可以遵循以下原则:

  1. PostgreSQL 默认使用 jsonb,MySQL 使用 JSON
  2. JSON 用于扩展字段,不要替代核心结构化字段。
  3. 高频查询字段优先独立建列或建立针对性索引。
  4. PostgreSQL 灵活查询 JSON 可使用 GIN,MySQL 通常使用生成列或函数索引。
  5. Go 简单场景使用 []byte + json.Unmarshal,复杂项目封装 Scanner/Valuer
  6. 注意 JSON 更新成本。MySQL 8 在满足条件时才支持 JSON 部分更新,而 PostgreSQL 的 jsonb 更新总是整值重写,因此:
    大型 JSON 文档如果频繁修改,容易产生明显的写放大,应将高频变化字段拆成独立列。

    PS:例如新增内容导致空间不足、更新方式不满足条件等情况,MySQL JSON 仍可能需要重写。

JSON 的价值在于灵活,而关系型数据库的价值在于结构和索引。两者结合得好,JSON 是扩展能力;结合不好,JSON 就会变成数据库里的黑盒。

如果你有魔法,你可以看到一个评论框~