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

Bos*_*osh 1 postgresql json jsonb postgresql-9.4

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

create table resources (id serial primary key, fields jsonb);
Run Code Online (Sandbox Code Playgroud)

它包含带键的数据(从大集中绘制)和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}
Run Code Online (Sandbox Code Playgroud)

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

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

...但当然这不使用索引,它会转向表扫描.我想知道是否有:

  1. 使用"polled"> 50查询资源的更有效方法
  2. 一种构建支持此类查询的索引的方法

Egg*_*ant 8

您没有指定INDEX您期望使用哪种类型,并且您没有提供它的定义.

典型的INDEX一个jsonb领域将是一个GIN之一,但在特定情况下,你需要实际比较中所含的某些价值观polled的关键.

也许具有表达式的特定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;
Run Code Online (Sandbox Code Playgroud)



编辑:

我做了一个快速测试,在我的评论中实践这个想法,GIN INDEX在实际比较值之前使用a 来限制行数,事实证明你GIN INDEX甚至可以在这种情况下使用a .

INDEX必须使用的缺省操作符来创建jsonb_ops (不是在更轻,更表演jsonb_path_ops):

CREATE INDEX ON resources USING GIN (fields);
Run Code Online (Sandbox Code Playgroud)

现在您可以利用索引简单地?在查询中包含一个存在的测试:

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;
Run Code Online (Sandbox Code Playgroud)

查询现在执行速度提高了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;
Run Code Online (Sandbox Code Playgroud)

只是给你一个想法,你的数据样本大约有300K行,表格大约250MB,包含90MB的数据和160MB的索引!就个人而言,我会坚持(我实际上)LATERAL JOIN没有索引的简单.