1

我已经定义了一个简单的表

create table resources (id serial primary key, fields jsonb);

它包含带有键的数据(从一个大集合中提取)和 1 到 100 之间的值,例如:

   id   |    fields                                                                                                 
--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
      1 | {"tex": 23, "blair": 46, "cubic": 50, "raider": 57, "retard": 53, "hoariest": 78, "suturing": 25, "apostolic": 22, "unloosing": 37, "flagellated": 85}
      2 | {"egoist": 75, "poshest": 0, "annually": 19, "baptists": 29, "bicepses": 10, "eugenics": 9, "idolizes": 8, "spengler": 60, "scuppering": 13, "cliffhangers": 37}
      3 | {"entails": 27, "hideout": 22, "horsing": 98, "abortions": 88, "microsoft": 37, "spectrums": 26, "dilettante": 52, "ringmaster": 84, "floweriness": 72, "vivekananda": 24}
      4 | {"wraps": 6, "polled": 68, "coccyges": 63, "internes": 93, "unburden": 61, "aggregate": 76, "cavernous": 98, "stylizing": 65, "vamoosing": 35, "unoriginal": 40}
      5 | {"villon": 95, "monthly": 68, "puccini": 30, "samsung": 81, "branched": 33, "congeals": 6, "shriller": 47, "terracing": 27, "patriarchal": 86, "compassionately": 94}

我想搜索其值(与特定键关联)大于某个基准值的条目。我可以做到这一点,例如通过:

with exploded as (
    select id, (jsonb_each_text(fields)).*
    from resources)
select distinct id
    from exploded
    where key='polled' and value::integer>50;

...但当然这不使用索引,而是使用表扫描。我想知道是否有:

  1. 一种更有效的方式来查询“polled”>50 的资源
  2. 一种构建支持此类查询的索引的方法
4

1 回答 1

7

您尚未指定INDEX您希望使用哪种类型,也没有提供它的定义。

字段的典型值INDEXGIN,但在您的特定情况下,您需要实际比较键中包含的一些值。jsonbpolled

也许带有表达式的特定INDEX(虽然不是GIN一个!)可能会有一些用处,但我怀疑它并且它可能会变得非常麻烦,因为您至少需要一个双精度类型转换来获得一个整数值和一个自定义函数来实际在您的语句中执行类型转换。IMMUTABLECREATE INDEX

在采取只解决一些特定情况的复杂路线之前(如果您需要与不同的fields键进行另一次比较怎么办?),您可以尝试优化您当前的查询,利用 PostgreSQL 9.4 新的LATERAL功能和jsonb处理功能。结果是一个查询应该比您当前的查询快 8 倍:

SELECT r.id 
    FROM resources AS r,
    LATERAL jsonb_to_record(r.fields) AS l(polled integer) 
    WHERE l.polled > 50;



编辑 :

我做了一个快速测试,将我评论中的想法付诸实践,在实际比较值之前使用 a来限制行数,结果证明即使在这种情况下GIN INDEX你也可以真正使用 a 。GIN INDEX

INDEX必须使用默认操作符类创建(jsonb_ops 不是更轻且性能更高的jsonb_path_ops

CREATE INDEX ON resources USING GIN (fields);

现在您可以利用索引,只需?在查询中包含一个存在测试:

SELECT r.id
    FROM resources AS r,
    LATERAL jsonb_to_record(r.fields) AS l(polled integer) 
    WHERE r.fields ? 'polled' AND l.polled > 50;

现在查询的执行速度快了大约3 倍 (比第一个 CTE 版本快大约 20 倍)。我已经测试了多达 1M 行,性能增益始终相同。


请记住,正如预期的那样,行数起着重要作用:如果行数少于 1K,则索引毫无用处,查询规划器可能不会使用它。

也不要忘记jsonb_ops与实际数据相比,索引可能会变得巨大。像你这样的数据样本,从 1K 到 1M 行,索引本身比表中的实际数据大170% 左右,自己检查一下:

SELECT pg_size_pretty(pg_total_relation_size('resources')) AS whole_table, 
       pg_size_pretty(pg_relation_size('resources')) AS data_only, 
       pg_size_pretty(pg_relation_size('resources_fields_idx')) AS gin_index_only;

只是给你一个想法,像你的数据样本一样大约有 300K 行,表大约是 250MB,由 90MB 的数据和 160MB 的索引组成!就个人而言,我会坚持(而且我确实这样做)LATERAL JOIN没有索引的简单。

于 2015-05-08T11:08:06.740 回答