关系型数据库支持 JSON 已经很成熟,但 JSON 并不意味着可以随意替代普通字段。实际项目中,更重要的问题是:PostgreSQL 的 json 和 jsonb 怎么选?MySQL JSON 如何建立索引?Go 又应该怎样读写?
PostgreSQL:绝大多数情况选 jsonb
PostgreSQL 同时提供 json 和 jsonb:
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 更适合作为 attributes、extra、metadata 一类的扩展字段。
数据库设计应该围绕查询模式,而不是围绕数据格式。
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 驱动通常通过
[]byte或string传递 JSON,但数据库内部并不一定按文本存储。例如 PostgreSQLjsonb和 MySQL JSON 都有自己的内部格式。 -
复杂项目:封装泛型类型,实现
sql.Scanner和driver.Valuer。type JSON[T any] struct { Val T }读取数据库时自动
json.Unmarshal,写入数据库时自动json.Marshal。这样业务代码可以直接使用:
JSON[UserConfig] JSON[map[string]any] JSON[[]string]相比每个地方手动序列化,这种方式更统一,也更适合长期维护。
总结
实际项目可以遵循以下原则:
- PostgreSQL 默认使用
jsonb,MySQL 使用JSON。 - JSON 用于扩展字段,不要替代核心结构化字段。
- 高频查询字段优先独立建列或建立针对性索引。
- PostgreSQL 灵活查询 JSON 可使用 GIN,MySQL 通常使用生成列或函数索引。
- Go 简单场景使用
[]byte + json.Unmarshal,复杂项目封装Scanner/Valuer。 -
注意 JSON 更新成本。MySQL 8 在满足条件时才支持 JSON 部分更新,而 PostgreSQL 的 jsonb 更新总是整值重写,因此:
大型 JSON 文档如果频繁修改,容易产生明显的写放大,应将高频变化字段拆成独立列。PS:例如新增内容导致空间不足、更新方式不满足条件等情况,MySQL JSON 仍可能需要重写。
JSON 的价值在于灵活,而关系型数据库的价值在于结构和索引。两者结合得好,JSON 是扩展能力;结合不好,JSON 就会变成数据库里的黑盒。