为什么Postgres在jsonb列上的查找速度如此之慢?

时间:2016-12-07 16:32:23

标签: postgresql indexing postgresql-9.4 jsonb indices

我的表格targeting包含marital_status类型的列text[]data类型的另一列jsonb。这两列的内容是相同的,只是采用不同的格式(仅用于演示目的)。示例数据:

 id |      marital_status      |                        data                       
----+--------------------------+---------------------------------------------------
  1 | null                     | {}
  2 | {widowed}                | {"marital_status": ["widowed"]}
  3 | {never_married,divorced} | {"marital_status": ["never_married", "divorced"]}
...

表中有超过690K的记录随机组合。

在text []列

上查找
EXPLAIN ANALYZE SELECT marital_status
FROM targeting
WHERE marital_status @> '{widowed}'::text[]

没有索引

通常需要<没有创建任何指数的900毫秒:

Seq Scan on targeting  (cost=0.00..172981.38 rows=159061 width=28) (actual time=0.017..840.084 rows=158877 loops=1)
  Filter: (marital_status @> '{widowed}'::text[])
  Rows Removed by Filter: 452033
Planning time: 0.150 ms
Execution time: 845.731 ms

使用索引

使用索引通常需要< 200毫秒(75%改进):

CREATE INDEX targeting_marital_status_idx ON targeting ("marital_status");

结果:

Index Only Scan using targeting_marital_status_idx on targeting  (cost=0.42..23931.35 rows=159061 width=28) (actual time=3.528..143.848 rows=158877 loops=1)"
  Filter: (marital_status @> '{widowed}'::text[])
  Rows Removed by Filter: 452033
  Heap Fetches: 0
Planning time: 0.217 ms
Execution time: 148.506 ms

在jsonb列上查找

EXPLAIN ANALYZE SELECT data
FROM targeting
WHERE (data -> 'marital_status') @> '["widowed"]'::jsonb

没有索引

通常需要< 5,700毫秒没有创建任何指数(慢6倍以上!):

Seq Scan on targeting  (cost=0.00..174508.65 rows=611 width=403) (actual time=0.095..5399.112 rows=158877 loops=1)
  Filter: ((data -> 'marital_status'::text) @> '["widowed"]'::jsonb)
  Rows Removed by Filter: 452033
Planning time: 0.172 ms
Execution time: 5408.326 ms

使用索引

使用索引通常需要< 3,700毫秒(改善35%):

CREATE INDEX targeting_data_marital_status_idx ON targeting USING GIN ((data->'marital_status'));

结果:

Bitmap Heap Scan on targeting  (cost=144.73..2482.75 rows=611 width=403) (actual time=85.966..3694.834 rows=158877 loops=1)
  Recheck Cond: ((data -> 'marital_status'::text) @> '["widowed"]'::jsonb)
  Rows Removed by Index Recheck: 201080
  Heap Blocks: exact=33723 lossy=53028
  ->  Bitmap Index Scan on targeting_data_marital_status_idx  (cost=0.00..144.58 rows=611 width=0) (actual time=78.851..78.851 rows=158877 loops=1)"
        Index Cond: ((data -> 'marital_status'::text) @> '["widowed"]'::jsonb)
Planning time: 0.257 ms
Execution time: 3703.492 ms

问题

  • 为什么text[]列的性能更高,即使不使用索引?
  • 为什么在jsonb列中添加索引只会使性能提高35%?
  • jsonb列上执行查找是否有更多的执行方式?

2 个答案:

答案 0 :(得分:1)

似乎是一个简单的问题。基本上你问的是怎么回事,

CREATE TABLE foo ( id int, key1 text );

快于

CREATE TABLE bar ( id int, jsonb foo );

@Craig在评论

中回答
  

GIN索引通常比b树效率低,所以预计会有很多。

此架构中的空值也应为

SELECT jsonb_build_object('marital_status',ARRAY[null]);
     jsonb_build_object     
----------------------------
 {"marital_status": [null]}
(1 row)

而不是{}。 PostgreSQL采用了许多快捷方式来快速更新jsonb对象,并使索引空间有效。

如果没有任何意义,请查看此伪表。

CREATE TABLE foo ( id int, x text, y text, z text )
CREATE INDEX ON foo(x);
CREATE INDEX ON foo(y);
CREATE INDEX ON foo(z);

这里我们有三个btree索引。让我们看一下类似的表格。

CREATE TABLE bar ( id int, junk jsonb );
CREATE INDEX ON bar USING gin (junk);
INSERT INTO bar (id,junk) VALUES (1,$${"x": 10, "y": 42}$$);

要使barfoo那样执行,我们需要两个双截面,这两个双截面都将比我们拥有的单个GIN索引大得多。如果你做了

INSERT INTO bar (id,junk) VALUES (1,$${"x": 10, "y": 42, "z":3}$$);

我们必须在z上有另一个btree索引,这个索引也会很大。你可以看到我要去哪里。 jsonb很棒,但索引和模式建模的复杂性并不与数据库并行。您不能将数据库缩减为jsonb列,发出CREATE INDEX并期望获得相同的性能。

答案 1 :(得分:0)

这可能是使用jsonb_ops(默认GIN索引策略)而不是jsonb_path_ops的问题。

根据文件: https://www.postgresql.org/docs/9.6/static/datatype-json.html

  

尽管jsonb_path_ops运算符类仅支持使用@>运算符的查询,但与默认运算符类jsonb_ops相比,它具有显着的性能优势。对于相同的数据,jsonb_path_ops索引通常比jsonb_ops索引小得多,并且搜索的特异性更好,特别是当查询包含频繁出现在数据中的键时。因此,搜索操作通常比默认操作符类更好。

     

jsonb_opsjsonb_path_ops GIN索引之间的技术差异是前者为数据中的每个键和值创建独立的索引项,而后者仅为每个值创建索引项数据。 [1]基本上,每个jsonb_path_ops索引项是值的哈希值和通向它的键;例如,索引{"foo": {"bar": "baz"}},将创建一个索引项,将foo,bar和baz中的所有三个合并到哈希值中。因此,查找此结构的包含查询将导致极其特定的索引搜索;但是根本没有办法找出foo是否作为关键。另一方面,jsonb_ops索引将分别创建表示foo,bar和baz的三个索引项;然后,要进行包含查询,它将查找包含所有这三个项的行。虽然GIN索引可以相当有效地执行这样的AND搜索,但它仍然不如同等jsonb_path_ops搜索更具体和更慢,特别是如果有非常多的行包含三个索引项中的任何一个。