索引jsonb以进行字段的数字比较

时间:2015-05-06 23:24:21

标签: json postgresql postgresql-9.4 jsonb

我用

定义了一个简单的表
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. 使用“轮询”> 50
  2. 查询资源的更有效方法
  3. 构建支持此类查询的索引的方法

1 个答案:

答案 0 :(得分:8)

您尚未指定您希望使用哪种INDEX,而您尚未提供该定义。

INDEX字段的典型jsonbGIN字段,但在您的具体情况下,您需要实际比较包含在其中的某些值polled密钥。

也许具有expression的特定INDEX(虽然不是GIN!)可能会有一些用处,但我对此表示怀疑,它可以变得非常麻烦,因为你需要至少一个double类型转换来获得一个整数值和一个自定义IMMUTABLE函数来实际执行CREATE 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;


编辑:

我做了一个快速测试,在我的评论中实践了这个想法,在实际比较值之前使用GIN INDEX来限制行数,事实证明你可以真正地使用一个{ {1}}即使在那种情况下也是如此。

必须使用默认运算符类GIN INDEX 创建INDEX更轻,性能更高jsonb_ops

jsonb_path_ops

现在,您可以利用索引在查询中简单地包含一个CREATE INDEX ON resources USING GIN (fields); 测试:

?

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


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

另外请不要忘记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; 索引与实际数据相比可能会变得很大。使用像您这样的数据样本,范围从1K到1M行,索引本身比表中的实际数据大 170%,请自行检查:

jsonb_ops

只是给你一个想法,你的数据样本大约有300K行,表大约250MB,包含90MB的数据和160MB的索引! 就个人而言,我会用一个没有索引的简单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; 来坚持(我实际上)