将JSONB用于除主键和外键以外的postgres列

dyn*_*ast 5 postgresql database-design jsonb

我刚刚发现了PostgreSQL的JSONB,想知道如果我将其用于所有表的列怎么办?

也就是说,我的所有表都将主键和外键作为列,而field对于其他任何数据,其列的类型均为JSONB。

除了会因为JSONB的开销而占用多余的空间,而且会丢失在“列”上的键入,我还会错过什么?

Sch*_*ern 6

事实证明,您正在这里做某事。

使用关系数据库的要点。

  • 明确定义的关系。
  • 定义明确且详细的架构。
  • 大型数据集的高性能。

您可以保持关系。但是您会丢失架构和很多性能。该模式不仅仅是数据验证。这意味着您不能在单个字段上使用触发器或约束。

至于性能……您会注意到,大多数JSONB性能测试都是针对其他类似数据类型的。他们从不反对普通的SQL表。这是因为,尽管JSONB的效率惊人,但其效率却不及常规SQL。因此,让我们对其进行测试,事实证明您正在这里进行一些工作。

使用此JSONB性能演示文稿中的数据集,我创建了正确的SQL模式...

create table customers (
    id text primary key
);

create table products (
    id text primary key,
    title text,
    sales_rank integer,
    "group" text,
    category text,
    subcategory text,
    similar_ids text[]
);

create table reviews (
    customer_id text references customers(id),
    product_id text references products(id),
    "date" timestamp,
    rating integer,
    votes integer,
    helpful_votes integer
);
Run Code Online (Sandbox Code Playgroud)

还有一种使用SQL关系但使用JSONB进行数据...

create table customers (
    id text primary key
);

create table products_jb (
    id text primary key,
    fields jsonb
);

create table reviews_jb (
    customer_id text references customers(id),
    product_id text references products_jb(id),
    fields jsonb
);
Run Code Online (Sandbox Code Playgroud)

和一个JSONB表。

create table reviews_jsonb (
    review jsonb
);
Run Code Online (Sandbox Code Playgroud)

然后,我使用一个小脚本将相同的数据导入到两组表中。589859评论,93319产品,98761客户。

让我们尝试与JSONB性能文章中的查询相同,以获取产品类别的平均评价。首先,没有索引。

传统SQL:138毫秒

test=> select round(avg(r.rating), 2)
from reviews r
join products p on p.id = r.product_id
where p.category = 'Home & Garden';
 round 
-------
  4.59
(1 row)

Time: 138.631 ms
Run Code Online (Sandbox Code Playgroud)

完整JSONB:380毫秒

test=> select round(avg((review#>>'{review,rating}')::numeric),2)
test-> from reviews_jsonb
test-> where review #>>'{product,category}' = 'Home & Garden';
 round 
-------
  4.59
(1 row)

Time: 380.697 ms
Run Code Online (Sandbox Code Playgroud)

混合JSONB:190毫秒

test=> select round(avg((r.fields#>>'{rating}')::numeric),2)
from reviews_jb r
join products_jb p on p.id = r.product_id
where p.fields#>>'{category}' = 'Home & Garden';
 round 
-------
  4.59
(1 row)

Time: 192.333 ms
Run Code Online (Sandbox Code Playgroud)

说实话,这比预期的要好。混合方法的速度是完整JSONB的两倍,但比普通SQL慢50%。现在如何处理索引?

传统SQL:130毫秒(索引+500毫秒)

test=> create index products_category on products(category);
CREATE INDEX
Time: 491.969 ms

test=> select round(avg(r.rating), 2)
from reviews r
join products p on p.id = r.product_id
where p.category = 'Home & Garden';
 round 
-------
  4.59
(1 row)

Time: 128.212 ms
Run Code Online (Sandbox Code Playgroud)

完整JSONB:360毫秒(索引+ 25000毫秒)

test=> create index on reviews_jsonb using gin(review);
CREATE INDEX
Time: 25253.348 ms
test=> select round(avg((review#>>'{review,rating}')::numeric),2)
from reviews_jsonb
where review #>>'{product,category}' = 'Home & Garden';
 round 
-------
  4.59
(1 row)

Time: 363.222 ms
Run Code Online (Sandbox Code Playgroud)

混合JSONB:185毫秒(索引为+6900毫秒)

test=> create index on products_jb using gin(fields);
CREATE INDEX
Time: 3654.894 ms
test=> create index on reviews_jb using gin(fields);
CREATE INDEX
Time: 3237.534 ms
test=> select round(avg((r.fields#>>'{rating}')::numeric),2)
from reviews_jb r
join products_jb p on p.id = r.product_id
where p.fields#>>'{category}' = 'Home & Garden';
 round 
-------
  4.59
(1 row)

Time: 183.679 ms
Run Code Online (Sandbox Code Playgroud)

事实证明,这对查询索引没有太大帮助。

这就是我看到的处理数据的方式,混合JSONB总是比Full SQL慢,但是比Full JSONB快。似乎是一个很好的折衷方案。您可以使用传统的外键和联接,但可以灵活地添加所需的任何字段。

我建议将混合方法更进一步:将SQL列用于您将要在那里使用的字段,并使用JSONB列来选择任何其他字段以提高灵活性。

我鼓励您在这里使用测试数据,看看性能如何。