内容提要
本文对比PostgreSQL与MySQL的JSON类型及Go处理方法。PostgreSQL中json保留原始文本,jsonb为二进制、支持GIN索引,多数场景应选jsonb;MySQL仅json一种,需生成列或多值索引。Go侧推荐自定义泛型JSON[T]类型实现Scanner/Valuer接口,统一处理读写与NULL,跨库项目可用GORM datatypes。核心建议:高频查询字段应提升为独立列,JSON适合结构多变的边缘数据。
延伸解读
jsonb 与 json 的选型边界
PostgreSQL 的 json 保留原始文本、空格和键顺序,写入快但查询慢且无法直接建 GIN 索引;jsonb 以二进制存储,支持 GIN 索引和 @> 等操作符,查询快但写入稍慢。文章建议绝大多数场景直接用 jsonb,只有需要原样存回(如审计留痕)时才用 json。MySQL 只有 json 一种类型,内部实现类似 jsonb,支持部分更新。
索引策略:PG 与 MySQL 的差异
PostgreSQL 可直接为 jsonb 建 GIN 索引,支持 @>、?、?|、?& 等操作符;jsonb_path_ops 索引更小更快但只支持 @>。MySQL 不能直接给 JSON 列建索引,需借助生成列加普通索引、8.0.13+ 的函数索引,或 8.0.17+ 的多值索引(专为 JSON 数组设计)。高频过滤字段应提升为独立列或表达式索引,避免全表扫描。
Go 侧统一处理:泛型 JSON[T] 包装器
Go 没有内建 JSON 列类型,但通过实现 driver.Valuer 和 sql.Scanner 接口即可读写。文章推荐自定义泛型 JSON[T] 包装器,一处定义即可复用于 struct 或 map,并处理 NULL。关键坑:Scan 中不能只断言 []byte,因为 lib/pq 传 []byte 而 pgx 传 string,MySQL 驱动也可能传 string,需同时处理两种类型。跨库项目可用 GORM datatypes.JSONType[T] 屏蔽差异。
JSON 与拆字段的权衡
JSON 适合结构多变、整体存取的边缘数据,如事件表因行而异的 payload、第三方 API 回包、稀疏属性等,可避免频繁 DDL 和 EAV 反模式。但高频 WHERE/JOIN/ORDER BY、需要 NOT NULL/CHECK/UNIQUE 约束、单字段频繁更新、存储紧凑或强类型场景,应拆为独立列。实践折中:热字段提拔为生成列或表达式索引,冷数据留在 JSON,且从 JSON 迁移到拆字段比反向更平滑。
Q&A
PostgreSQL 的 json 和 jsonb 类型有什么区别?
json 保留原始文本(包括空格和键顺序),写入快但查询慢,不支持索引;jsonb 是二进制存储,不保留空格和键顺序,写入稍慢但查询快,支持 GIN 索引。绝大多数场景应选 jsonb,只有需要原样存回(如审计)时才用 json。
MySQL 的 JSON 列如何建索引?
MySQL 不能直接给 JSON 列建索引,需借助生成列、函数索引或多值索引。例如:生成列 + 普通索引(ALTER TABLE ... ADD COLUMN ... AS (payload ->> '$.user') STORED, ADD INDEX);函数索引(MySQL 8.0.13+);多值索引(8.0.17+)专为 JSON 数组设计,之后可用 MEMBER OF 或 JSON_CONTAINS 命中索引。
在 Go 中如何优雅地处理数据库的 JSON 列?
推荐自定义泛型类型 JSON[T],实现 driver.Valuer 和 sql.Scanner 接口。Value 方法用 json.Marshal 将 T 序列化为 []byte;Scan 方法处理 []byte 和 string 两种来源,反序列化到 T。这样任意结构体或 map 都能直接作为 JSON 列读写,兼顾类型安全。
Go 中处理 JSON 列时如何应对 NULL 值?
可以定义 NullJSON[T] 包装类型,包含 Val T 和 Valid bool 字段。Value 方法在 Valid 为 false 时返回 nil;Scan 方法在 src 为 nil 时设置 Valid 为 false,否则反序列化并设置 Valid 为 true。这样就能安全处理数据库中的 NULL。
什么时候应该把 JSON 字段拆成独立的列?
当字段需要高频 WHERE、JOIN、ORDER BY、聚合,或需要 NOT NULL、CHECK、UNIQUE、外键等约束,或单字段频繁更新、要求存储紧凑、强类型时,应拆成独立列。JSON 适合结构多变、整体存取的边缘数据。
跨库项目(同时用 PostgreSQL 和 MySQL)在 Go 中如何处理 JSON 列?
可以使用 GORM 的 datatypes.JSONType[T] 泛型类型,它能自动处理两库差异,建表时映射为 jsonb 或 json。查询时用 datatypes.JSONQuery 构造跨库表达式。这样同一份代码兼容 PostgreSQL 和 MySQL。