必须满足读多写少、过滤极少依赖JSON内部字段、业务不依赖数据库强类型约束三个前提;PostgreSQL优先选jsonb并用表达式索引或GIN索引,MySQL JSON不适合作为查询字段,需警惕数据契约滑向应用层。

直接用 JSON 字段替代多列冗余字段,不是“能用就行”,而是必须满足三个前提:查询模式以读为主、过滤条件极少落在 JSON 内部字段上、业务逻辑不依赖数据库层的强类型约束。否则很快会遇到索引失效、类型校验缺失、JOIN 困难等问题。
JSON 字段在 PostgreSQL 中的 jsonb vs json
优先选 jsonb —— 它支持 GIN 索引、可按路径查询(如 data->>'status')、自动去空白和排序键名,写入时稍慢但读取和查询效率高得多。json 只保留原始格式,无法索引内部字段,也不支持 @> 或 ? 这类操作符。
常见误用:json 类型字段加了 GIN 索引却查不出结果,因为 GIN 默认只对 jsonb 生效。
- 建表时明确指定
data jsonb,别省略b - 插入前用
to_jsonb()转换,避免隐式转换失败 - 应用层序列化时关闭 key 排序(如 Python 的
sort_keys=False),否则和jsonb内部表示不一致,影响等值判断
如何给 JSON 内部字段加索引(PostgreSQL)
不能对整个 JSON 字段建普通 B-tree 索引;必须用表达式索引或 GIN + path 操作符组合。例如要高频查 data->>'category':
CREATE INDEX idx_data_category ON products USING btree ((data->>'category'));
若需模糊匹配或存在多个路径查询,改用 GIN:
CREATE INDEX idx_data_gin ON products USING gin (data);
注意:gin (data) 索引体积大、写入开销高,且无法加速 ORDER BY data->>'price' 这类排序——B-tree 表达式索引才支持排序。
- 单字段高频等值查询 → B-tree 表达式索引
- 多路径存在性判断(如
data ? 'tags')→ GIN 索引 - 范围查询(如价格区间)→ 必须提取为独立列,JSON 不适合
MySQL 8.0+ 的 JSON 字段限制更明显
MySQL 的 JSON 类型不支持原生 GIN 索引,只能靠虚拟列(GENERATED COLUMN)+ 普通索引兜底。例如:
ALTER TABLE products ADD COLUMN category AS (data->>'$.category') STORED;
CREATE INDEX idx_category ON products(category);
问题在于:每次新增一个查询字段,就得加一列、建一索引,DDL 变得频繁;且 STORED 列占用额外磁盘空间,VIRTUAL 列又无法索引(MySQL 8.0.13+ 才支持虚拟列索引,但仍有兼容性风险)。
- MySQL 下 JSON 更适合作为“归档字段”而非“查询字段”
- 如果业务需要按 JSON 内容筛选,优先考虑拆成宽表,或迁移到 PostgreSQL
-
JSON_CONTAINS()和JSON_EXTRACT()性能差,别在 WHERE 里嵌套多层调用
最常被忽略的一点:JSON 字段让“数据契约”从数据库层滑向应用层。没有 CHECK 约束、没有 NOT NULL 语义、也没有外键关联能力。一旦前端传错结构(比如把 "count": "5" 当字符串塞进去),后端不校验就存了,后面所有统计和导出都可能出错——这种问题不会报错,只会悄悄歪掉。










