每天分享技术栈,开发工具等
不知道大家在工作中遇没遇到过这种蛋疼的情况:要做一个商品表,手机、衣服、家电啥都卖。手机有“屏幕尺寸”、“CPU型号”,衣服有“尺码”、“材质”,家电有“功率”、“能效等级”。。。我的天,这表的字段得设计多少个?难道来个新品类就得ALTER TABLE ADD COLUMN
?那也太不灵活了,后期维护起来想死的心都有。
别慌,今天咱们就来聊聊PG里的大杀器——JSONB,专门治这种“字段不固定”的疑难杂症。它就像给你数据表里开了一个“万能口袋”,啥格式化的属性都能往里塞,而且查起来还贼快!
在开干之前,咱们得先分清两个“双胞胎”兄弟:JSON
和JSONB
。
PG里同时支持这两种类型,但咱们“IT咸鱼”要讲究,肯定得用那个更好的,对吧?
Binary
(二进制)的意思。你存数据的时候,PG会把它解析成一种优化的二进制格式。它不保留多余的空格和key的顺序,但换来的是极快的处理速度和强大的索引支持。一句话总结:如果你不需要保留JSON原始文本的每个字节(比如空格),只想高效地存取和查询数据,无脑用JSONB就对了!在咱们99%的场景里,JSONB都是最优选。
光说不练假把式。打开你的数据库连接工具(我这里用的是DataGrip,强烈推荐,谁用谁知道),咱们连上之前部署好的PG数据库,准备动手。
我假设你用的PG版本是比较新的,比如PostgreSQL 16,我这边也是。不过别担心,JSONB的核心功能在PG 9.4之后的版本里就很成熟了,大部分操作都是通用的。你可以在DataGrip的查询控制台里执行 SELECT version();
看看你的版本。
-- 在DataGrip的查询控制台或psql命令行里执行
SELECT version();
这个命令会返回类似这样的信息,让你对自己的环境心中有数 。 PostgreSQL 16.0 (Debian 16.0-1.pgdg120+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit
好,现在咱们来创建一个商品表products
。它有几个固定字段,比如自增ID、商品名、创建时间,然后最重要的,来一个JSONB
类型的字段叫attrs
(attributes的缩写),用来装那些千变万化的商品属性。
-- 在DataGrip的查询控制台执行
-- 创建一个商品表
CREATETABLE products (
id SERIALPRIMARYKEY,-- 自增ID,主键
name VARCHAR(255)NOTNULL,-- 商品名,不能为空
attrs JSONB,-- 我们的主角:JSONB类型的“万能口袋”
created_at TIMESTAMPTZ DEFAULTNOW()-- 创建时间,带时区,默认为当前时间
);
-- 给小白的注释:
-- SERIAL: 这是PG特有的,会自动创建一个自增的整数序列,非常适合做主键。
-- PRIMARY KEY: 声明id是主键,保证唯一且非空。
-- VARCHAR(255): 存最多255个字符的字符串。
-- TIMESTAMPTZ: 带时区的时间戳,可以避免时区混乱问题。
表建好了,咱们来插几条数据进去,模拟一下真实的商品信息 。
-- 在DataGrip的查询控制台执行
-- 插入一条手机数据
INSERTINTO products (name, attrs)VALUES
('SuperPhone Pro Max','{
"brand": "IT咸鱼",
"price": 8999.00,
"in_stock": true,
"tags": ["旗舰", "新品", "5G"],
"specs": {
"screen": "6.7 inch OLED",
"cpu": "S1 Chip",
"storage": "256GB"
}
}');
-- 插入一条T恤数据
INSERTINTO products (name, attrs)VALUES
('简约印花T恤','{
"brand": "咸鱼优品",
"price": 99.00,
"in_stock": true,
"tags": ["纯棉", "夏季"],
"specs": {
"color": "白色",
"size": "L"
}
}');
-- 插入一条缺货的笔记本电脑数据
INSERTINTO products (name, attrs)VALUES
('GamerBook X1','{
"brand": "IT咸鱼",
"price": 12999.00,
"in_stock": false,
"tags": ["游戏本", "RTX"],
"specs": {
"screen": "16 inch 165Hz",
"cpu": "Core i9",
"storage": "1TB SSD"
}
}');
看,是不是很灵活?手机和T恤的specs
结构完全不同,但都能轻松存到attrs
这个“口袋”里。
存进去只是第一步,能方便地查出来才是王道。JSONB有一套超级好用的操作符,咱们捡几个最常用的来盘一盘。
->
和 ->>
:取单层数据这是最基础的操作符,用来获取JSON对象里的某个key对应的值。
->
:返回的结果是JSON类型。->>
:返回的结果是TEXT类型。啥意思呢?看例子就懂了。
-- 在DataGrip的查询控制台执行
-- 例子1:获取所有商品的品牌 (brand)
-- 使用 ->>,因为我们通常需要的是文本格式的品牌名
SELECT
name,
attrs ->>'brand'AS brand_name -- 使用 ->> 直接获取文本
FROM
products;
DataGrip截图效果:
看,->>
直接把品牌名给你干干净净地取出来了。那如果用->
呢?
-- 在DataGrip的查询控制台执行
-- 对比一下 -> 和 ->> 的区别
SELECT
name,
attrs ->'price'AS price_json,-- 使用 -> 获取的是JSON数字
attrs ->>'price'AS price_text -- 使用 ->> 获取的是文本
FROM
products
WHERE
name ='SuperPhone Pro Max';
你会看到,price_json
列返回的是8999.00
(一个JSON数字),而price_text
列返回的是'8999.00'
(一个字符串)。
记住这个关键区别:当你想把取出的值用在WHERE
条件里和文本比较,或者直接显示给用户时,用->>
;如果你想对取出的JSON值继续进行JSON操作,那就用->
。
#>
和 #>>
:深入嵌套,直捣黄龙如果我想获取specs
里面的cpu
型号咋办?specs
本身就是个嵌套的JSON对象。这时候就轮到路径操作符上场了。
#>
:按路径获取,返回JSON类型。#>>
:按路径获取,返回TEXT类型。路径用一个文本数组来表示,比如'{specs, cpu}'
就代表先找specs
,再在specs
里找cpu
。
-- 在DataGrip的查询控制台执行
-- 获取SuperPhone的CPU型号
SELECT
name,
attrs #>> '{specs,cpu}' AS cpu_model -- 一步到位,获取嵌套的CPU型号
FROM
products
WHERE
name ='SuperPhone Pro Max';
这一下就能拿到 'S1 Chip'
,是不是很方便?
@>
:终极杀器,包含查询这个操作符简直是神技!它的意思是“包含”。可以判断一个JSONB值是否包含了另一个JSONB值。这在筛选数据时极其有用。
-- 在DataGrip的查询控制台执行
-- 例子1:查询所有品牌是“IT咸鱼”的商品
-- '{"brand": "IT咸鱼"}' 这个小JSON,是不是被 attrs 这个大JSON包含了?
SELECT name, attrs ->>'price'as price
FROM products
WHERE attrs @>'{"brand": "IT咸鱼"}';
-- 例子2:查询所有库存为 true 的商品
SELECT name, attrs ->>'price'as price
FROM products
WHERE attrs @>'{"in_stock": true}';
-- 例子3:查询标签(tags)里包含“新品”的商品
-- 注意,对于数组,它会判断右边的数组元素是否都在左边的数组里
SELECT name
FROM products
WHERE attrs ->'tags' @>'["新品"]';
@>
操作符的强大之处在于,它可以深入到嵌套结构,也可以查询数组,几乎满足了我们日常80%的复杂查询需求。
为了让你感受一下PG官方文档的强大,下面这张图截取自官方文档页面,展示了部分JSON相关的函数和操作符。没事多去翻翻,宝藏很多!
jsonb_set
函数查会了,那改呢?比如我想把“GamerBook X1”的价格改一下,或者给它加个“内存”属性。
这时候就要用到jsonb_set
函数了。它的用法稍微复杂点,但逻辑很清晰:
jsonb_set(target_jsonb, path_array, new_value_jsonb, [create_if_missing])
target_jsonb
: 你要操作的那个JSONB字段,比如attrs
。path_array
: 一个路径数组,告诉函数你要改哪个位置,比如'{price}'
。new_value_jsonb
: 你要设置的新值,必须是jsonb格式,比如'11999.00'::jsonb
。create_if_missing
: 一个可选的布尔值。如果是true
,路径不存在时会自动创建;如果是false
(默认),路径不存在就不做任何事。
-- 在DataGrip的查询控制台执行
-- 例子1:把GamerBook X1的价格从12999改成11999
UPDATE products
SET attrs = jsonb_set(attrs,'{price}','11999.00'::jsonb)
WHERE name ='GamerBook X1';
-- 例子2:给GamerBook X1增加一个内存(memory)属性
-- 因为原来没有memory,所以第四个参数要设为true
UPDATE products
SET attrs = jsonb_set(attrs,'{specs,memory}','"32GB DDR5"'::jsonb,true)
WHERE name ='GamerBook X1';
-- 检查一下修改结果
SELECT name, attrs FROM products WHERE name ='GamerBook X1';
执行完上面的语句,你会发现GamerBook X1的价格和属性都已经被成功更新了,非常精准!
JSONB的查询很快,但当你的数据量达到几十万、几百万时,即便是二进制也扛不住全表扫描。想让你的@>
查询快到飞起吗?你必须给它建一个GIN索引。
-- 在DataGrip的查询控制台执行
CREATEINDEX idx_products_attrs_gin ON products USING GIN (attrs);
就这么简单一行代码,PG就会为attrs
字段里的所有键值对创建一个高效的反向索引。之后,你再用@>
去查询,PG就能通过索引秒级定位到数据,而不是傻傻地去扫描整张表 。
记住:只要你准备在JSONB字段上频繁使用@>
、?
、?|
或?&
这些操作符进行查询,一定要创建GIN索引!
怎么样,是不是感觉JSONB也没有那么神秘?它只是一个工具,一个能帮你优雅地解决“动态字段”问题的强大工具。作为一名立志成为优秀DBA的“IT咸鱼”,熟练掌握JSONB绝对能让你的技术栈更有深度。
今天的分享就到这里,赶紧打开你的DataGrip亲自敲一遍试试吧!实践出真知,我们下期再见!