Postgres jsonb 与复合类型的性能差异

Met*_*lis 9 postgresql database-design datatypes composite-types postgresql-10

在 jsonb 列和相同结构的复合类型列之间进行选择需要考虑哪些因素?

例如,考虑 Postgres 文档中使用的类似列:

CREATE TYPE inventory_item AS (
name            text,
supplier_id     integer,
price           numeric
);
Run Code Online (Sandbox Code Playgroud)

这种方法与镜像这种结构的 jsonb 列之间的权衡是什么?

例如,我怀疑复合类型不需要存储每条记录的键名,而 jsonb 类型需要这样做。

Eva*_*oll 5

其实这不是性能问题

  • 复合类型是强类型的,这意味着您必须定义它们的形状。
  • JSON 是弱类型的。

此外,JSON 有一个用例,但对于复合类型,您几乎不想使用它们,因为将它们规范化为它们自己的关系几乎总是更好。但有一个例外——当您要围绕一组数据创建整个库时,复合类型可以使一切变得更好。例如PostGIS 中的stdaddr就是这样的一个例子。该类型表示物理地址,并且多种事物可以采用该类型(例如 PostGIS 地理编码器)。

所以你可以做

CREATE TABLE foo ( foo_id int, address postgis.stdaddr );
Run Code Online (Sandbox Code Playgroud)

现在您可以相对轻松地获取地理编码信息,并且只需传递一个值而不是 15


Pet*_*uss 5

问题中定义了TYPE inventory_item (与指南中相同),因此我们只需定义具有复合(ROW)类型的表tc和具有 JSONb 类型的表tj

插入时间

-- drop table tc; drop table tj;
CREATE TABLE tc (id serial, x inventory_item);
CREATE TABLE tj (id serial, x JSONb);

EXPLAIN ANALYSE 
  INSERT INTO tc(x) VALUES 
    (ROW('fuzzy dice', 42, 1.99)),
    (ROW('test pi', 3, 3.1415))
; -- Execution Time: try1 0.386 ms; try2 0.559 ms; try3 0.102 ms; ... 
EXPLAIN ANALYSE 
  INSERT INTO tj(x) VALUES 
    ('{"name":"fuzzy dice", "supplier_id":42, "price":1.99}'::jsonb),
    ('{"name":"test pi", "supplier_id":3, "price":3.1415}'::jsonb)
; -- Execution Time: try1 0.343; try2 0.355 ms; try3 0.112 ms; ...
Run Code Online (Sandbox Code Playgroud)

当然,我们需要循环等一些复杂的东西来测试......但似乎“可比”时间,没有太大区别。

选择本地时间

仅检索原始数据类型。需要良好的基准,但让我们想象一些简单的事情只是为了检查大的差异。

EXPLAIN ANALYSE   SELECT x, i FROM tc, generate_series(1,999999) g(i); 
EXPLAIN ANALYSE   SELECT x, i FROM tj, generate_series(1,999999) g(i); 
Run Code Online (Sandbox Code Playgroud)

又没有区别。两者都带有“执行时间:~460”。

爆炸时间

EXPLAIN ANALYSE
  SELECT i, id, (x).name, (x).supplier_id, (x).price
  FROM tc, generate_series(1,999999) g(i)
; -- Execution Time: ~490 ms 
EXPLAIN ANALYSE
  SELECT i, tj.id, t.* 
  FROM tj, generate_series(1,999999) g(i),
       LATERAL jsonb_populate_record(null::inventory_item, tj.x) t  
; -- Execution Time: ~650 ms
Run Code Online (Sandbox Code Playgroud)

看起来将 JSONb 对象转换为 SQL 行非常快!看起来是二进制转换:我们可以假设该函数jsonb_populate_record通过inventory_item内部定义将 JSONb 类型映射到 SQL。

而且它比复合表更快

爆炸并计算一些东西

EXPLAIN ANALYSE
  SELECT i, (x).supplier_id+i, (x).price+0.01
  FROM tc, generate_series(1,999999) g(i)
; -- Execution Time: ~800 ms

EXPLAIN ANALYSE
  SELECT i,  t.supplier_id+i, t.price+0.01
  FROM tj, generate_series(1,999999) g(i),
       LATERAL jsonb_populate_record(null::inventory_item, tj.x) t  
; -- Execution Time: ~620 ms
Run Code Online (Sandbox Code Playgroud)

也许计算需要约 150 毫秒,所以预期时间相同...上面的示例中存在一些错误,需要更好的基准来检查真正的差异。


检查从文本投射的相对时间。

EXPLAIN ANALYSE -- (supposed to) cast from binary
  SELECT i, id, x->>'name' as name, 
         (x->'supplier_id')::int as supplier_id, (x->'price')::float as price
  FROM tj, generate_series(1,999999) g(i)
; -- Execution Time: ~1600 ms 

EXPLAIN ANALYSE -- cast from text
  SELECT i, id, x->>'name' as name, 
         (x->>'supplier_id')::int as supplier_id, (x->>'price')::float as price
  FROM tj, generate_series(1,999999) g(i)
; -- Execution Time: ~1600 ms 
Run Code Online (Sandbox Code Playgroud)

时间长且相同。似乎(x->'supplier_id')::int它只是(x->>'supplier_id')::int or的糖语法(x->'supplier_id')::text::int

PS:这个答案也是对另一个问题的补充,关于“使用 JSONb 进行二进制到二进制转换”。