我正在设计一个 postgres 数据库,其中有一个包含 jsonb 类型列的表。我希望这个专栏是独一无二的。表中不需要有两个具有完全相同 json 配置的对象。对于未保存在数据库中的每个副本,它将为我节省大约 5 分钟的计算时间。我意识到 json 唯一性在字典方面的风险(不能保证键的顺序),但我认为一个好的 json 编码器可以缓解这个问题。
我担心的是数据库性能。我想确保我们正在尽一切可能确保插入不会因 jsonb 的唯一性约束而严重减慢。与 varchar 或 int 上的唯一性约束相比,jsonb 上的唯一性约束有多糟糕?我们说的是毫秒、秒还是分钟?
我研究了哈希索引,这听起来确实是我获得最佳性能所需的一切。但。只有B树类型的索引可以是唯一的,这很奇怪。为什么?
字典存在 json 唯一性的风险(不保证键的顺序)
JSONB
确实保证了键的顺序。它还会忽略无关紧要的空格,并对内部排序的键进行重复数据删除。你计划做的事情将会很好地进行。
如果您想使用普通的
json
,我会担心它的工作方式与预先验证的 text
非常相似,并且会保留您保存到其中的所有内容,从而使 {"a":1}
和 { "a" : 1 }
不平等。此外,由于这个原因,没有内置的 json=json
运算符,因此您无法定义 unique
类型的 json
列。
性能方面,你要自己测试一下,看看是否可以接受。强制执行限制总是会产生一些成本,无论如何它都不是免费的。
演示select '{"a":1}'::jsonb = E' {\n\t "a" : 1 \n} '::jsonb
?栏? |
---|
t |
select '{"a":1}'::json = E' {\n\t "a" : 1 \n} '::json;
ERROR: operator does not exist: json = json LINE 1: select '{"a":1}'::json = E' {\n\t "a" : 1 \n} '::json; ^ HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
create table test_no_unique(a jsonb);
select setseed(.42);
explain analyze verbose
insert into test_no_unique
select jsonb_build_object(n::text,n)
from generate_Series(1,1e5)n;
查询计划 |
---|
在public.test_no_unique上插入(成本=0.00..17.50行=0宽度=0)(实际时间=724.632..724.633行=0循环=1) |
-> pg_catalog.generate_series n 上的函数扫描(成本=0.00..17.50行=1000宽度=32)(实际时间=33.737..354.927行=100000循环=1) |
输出:jsonb_build_object((n.n)::text, n.n) |
函数调用:generate_series('1'::numeric, '100000'::numeric) |
规划时间:0.068毫秒 |
执行时间:725.363 ms |
create table test_unique_no_onconflict(a jsonb unique);
select setseed(.42);
explain analyze verbose
insert into test_unique_no_onconflict
select jsonb_build_object(n::text,n)
from generate_Series(1,1e5)n;
查询计划 |
---|
在public.test_unique_no_onconflict上插入(成本=0.00..17.50行=0宽度=0)(实际时间=1953.638..1953.639行=0循环=1) |
-> pg_catalog.generate_series n 上的函数扫描(成本=0.00..17.50行=1000宽度=32)(实际时间=64.094..392.841行=100000循环=1) |
输出:jsonb_build_object((n.n)::text, n.n) |
函数调用:generate_series('1'::numeric, '100000'::numeric) |
规划时间:0.047 ms |
执行时间:1955.242 ms |
create table test_unique_on_conflict_do_nothing_without_dupes(a jsonb unique);
select setseed(.42);
explain analyze verbose
insert into test_unique_on_conflict_do_nothing_without_dupes
select jsonb_build_object(n::text,n)
from generate_Series(1,1e5)n
on conflict do nothing;
查询计划 |
---|
插入public.test_unique_on_conflict_do_nothing_without_dupes(成本=0.00..17.50行=0宽度=0)(实际时间=5938.022..5938.023行=0循环=1) |
解决冲突:没什么 |
插入的元组:100000 |
冲突元组:0 |
-> pg_catalog.generate_series n 上的函数扫描(成本=0.00..17.50行=1000宽度=32)(实际时间=33.093..527.146行=100000循环=1) |
输出:jsonb_build_object((n.n)::text, n.n) |
函数调用:generate_series('1'::numeric, '100000'::numeric) |
规划时间:0.043毫秒 |
执行时间:5938.573 ms |
这里,请注意,与之前的示例相比,调用 random() 并进行强制转换会花费一些成本,处理冲突也花费了一些成本,但通过有效地将 36% 的行写入表中,它也节省了一些成本。
create table test_unique_on_conflict_do_nothing_with_dupes(a jsonb unique);
select setseed(.42);
explain analyze verbose
insert into test_unique_on_conflict_do_nothing_with_dupes
select jsonb_build_object((random()*1e3)::int::text,(random()*1e2)::int::text)
from generate_Series(1,1e5)n
on conflict do nothing;
查询计划 |
---|
插入public.test_unique_on_conflict_do_nothing_with_dupes(成本=0.00..47.50行=0宽度=0)(实际时间=5067.804..5067.805行=0循环=1) |
解决冲突:没什么 |
插入的元组:63241 |
冲突元组:36759 |
-> pg_catalog.generate_series n 上的函数扫描(成本=0.00..37.50行=1000宽度=32)(实际时间=66.536..491.087行=100000循环=1) |
输出:jsonb_build_object((((random() * '1000'::双精度))::integer)::text, (((random() * '100'::双精度))::integer): :文字) |
函数调用:generate_series('1'::numeric, '100000'::numeric) |
规划时间:0.121毫秒 |
执行时间:5082.982 ms |