JSONB 数据类型
问题
PostgreSQL 的 JSONB 类型有哪些特性?如何查询和索引 JSONB 数据?JSONB 能替代 MongoDB 吗?
答案
一、JSON vs JSONB
| 对比项 | JSON | JSONB |
|---|---|---|
| 存储方式 | 文本原样存储 | 二进制解析后存储 |
| 写入速度 | 快(不解析) | 稍慢(需解析) |
| 读取速度 | 慢(每次读取需解析) | 快(已解析) |
| 保留格式 | 保留空格、键顺序 | 不保留 |
| 支持索引 | 不支持 | 支持 GIN 索引 |
| 推荐 | 仅存储不查询 | 几乎所有场景 |
结论
除非需要保留 JSON 原始格式,否则一律使用 JSONB。
二、JSONB 操作符
提取操作符
-- 创建示例表
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT,
attrs JSONB -- 使用 JSONB 存储灵活属性
);
INSERT INTO products (name, attrs) VALUES
('iPhone 15', '{"brand": "Apple", "price": 7999, "colors": ["black", "blue"], "specs": {"ram": "8GB", "storage": "256GB"}}');
-- -> 提取 JSON 对象(返回 JSONB 类型)
SELECT attrs -> 'brand' FROM products; -- "Apple"(带引号)
-- ->> 提取文本(返回 TEXT 类型)
SELECT attrs ->> 'brand' FROM products; -- Apple(不带引号)
-- #> 路径提取(JSONB)
SELECT attrs #> '{specs, ram}' FROM products; -- "8GB"
-- #>> 路径提取(TEXT)
SELECT attrs #>> '{specs, ram}' FROM products; -- 8GB
-- 数组元素提取
SELECT attrs -> 'colors' -> 0 FROM products; -- "black"
条件查询操作符
-- @> 包含(最常用)
SELECT * FROM products WHERE attrs @> '{"brand": "Apple"}';
-- <@ 被包含
SELECT * FROM products WHERE '{"brand": "Apple", "price": 7999}' <@ attrs;
-- ? 键是否存在
SELECT * FROM products WHERE attrs ? 'brand';
-- ?| 任一键存在
SELECT * FROM products WHERE attrs ?| array['brand', 'color'];
-- ?& 所有键存在
SELECT * FROM products WHERE attrs ?& array['brand', 'price'];
修改操作符
-- || 合并
UPDATE products SET attrs = attrs || '{"weight": "187g"}' WHERE id = 1;
-- - 删除键
UPDATE products SET attrs = attrs - 'weight' WHERE id = 1;
-- #- 删除路径
UPDATE products SET attrs = attrs #- '{specs, ram}' WHERE id = 1;
-- jsonb_set 设置嵌套值
UPDATE products
SET attrs = jsonb_set(attrs, '{specs, ram}', '"12GB"')
WHERE id = 1;
三、JSONB 索引
GIN 索引(最重要)
-- 通用 GIN 索引(支持 @>, ?, ?|, ?& 操作符)
CREATE INDEX idx_attrs ON products USING GIN (attrs);
-- 路径操作类 GIN 索引(仅支持 @>,更小更快)
CREATE INDEX idx_attrs_path ON products USING GIN (attrs jsonb_path_ops);
| 索引类型 | 支持操作符 | 索引大小 | 适用场景 |
|---|---|---|---|
| GIN (默认) | @>, ?, `? | , ?&` | 较大 |
| GIN (jsonb_path_ops) | 仅 @> | 较小 | 只需要包含查询 |
表达式索引
-- 对特定字段建 B-tree 索引
CREATE INDEX idx_brand ON products ((attrs ->> 'brand'));
-- 对数值字段建索引(注意类型转换)
CREATE INDEX idx_price ON products (((attrs ->> 'price')::int));
-- 查询时使用
SELECT * FROM products WHERE attrs ->> 'brand' = 'Apple'; -- 走 idx_brand
SELECT * FROM products WHERE (attrs ->> 'price')::int > 5000; -- 走 idx_price
索引选择
- 查询模式明确(如固定查某个字段)→ 用表达式索引,性能最好
- 查询模式灵活(不确定查哪些字段)→ 用 GIN 索引
四、JSONB 常用函数
-- 键值对展开
SELECT * FROM jsonb_each('{"a": 1, "b": 2}');
-- key | value
-- a | 1
-- b | 2
-- 获取所有键
SELECT jsonb_object_keys('{"a": 1, "b": 2}');
-- 数组元素展开
SELECT jsonb_array_elements('[1, 2, 3]');
-- 构建 JSONB
SELECT jsonb_build_object('name', '张三', 'age', 25);
-- {"age": 25, "name": "张三"}
-- 聚合为 JSONB 数组
SELECT jsonb_agg(name) FROM products;
-- 聚合为 JSONB 对象
SELECT jsonb_object_agg(name, attrs ->> 'price') FROM products;
-- 类型判断
SELECT jsonb_typeof(attrs -> 'price') FROM products; -- number
SELECT jsonb_typeof(attrs -> 'colors') FROM products; -- array
五、JSONB 最佳实践
| 场景 | 建议 |
|---|---|
| 固定字段 | 用普通列,不要放 JSONB |
| 灵活属性 | 适合 JSONB(如商品属性、配置) |
| 嵌套层级 | 建议不超过 3~4 层 |
| 数组元素很多 | 考虑拆为关联表 |
| 需要聚合分析 | JSONB 性能不如普通列 |
六、JSONB vs MongoDB
| 对比项 | PostgreSQL + JSONB | MongoDB |
|---|---|---|
| 数据模型 | 关系+文档混合 | 纯文档 |
| 事务 | 完整 ACID 事务 | 4.0+ 支持多文档事务 |
| JOIN | 支持 | 不支持(需 $lookup) |
| 灵活性 | 很好 | 更好 |
| 索引 | GIN + 表达式索引 | 丰富的索引类型 |
| 分片 | Citus 扩展 | 原生分片 |
| 适用场景 | 80% 关系 + 20% 文档 | 纯文档型数据 |
常见面试问题
Q1: 什么场景适合用 JSONB?
答案:
- 灵活属性:商品的自定义属性(不同类目属性不同)
- 配置存储:用户设置、系统配置
- API 响应缓存:缓存第三方 API 的 JSON 响应
- 日志/事件数据:Schema 不固定的半结构化数据
- 快速原型:早期开发阶段 Schema 还在演化
Q2: JSONB 的查询性能怎么样?
答案:
- 不建索引:和全表扫描无异
- GIN 索引:
@>包含查询性能很好 - 表达式索引:特定字段查询接近普通列性能
- 聚合计算:不如普通列(需要运行时提取)
生产建议:高频查询字段建表达式索引,灵活查询用 GIN 索引。
Q3: JSONB 存储大小有限制吗?
答案:
单个 JSONB 值最大 256MB(受 TOAST 限制)。但实际使用中,JSONB 太大会影响性能。建议单个 JSONB 值不超过几十 KB,大文档考虑拆分。