索引 jsonb 用于字段的数字比较

Posted

技术标签:

【中文标题】索引 jsonb 用于字段的数字比较【英文标题】:Indexing jsonb for numeric comparison of fields 【发布时间】:2015-05-06 23:24:21 【问题描述】:

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

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;

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

    一种更有效的“轮询”>50 资源查询方式 一种构建支持此类查询的索引的方法

【问题讨论】:

【参考方案1】:

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

jsonb 字段的典型INDEX 是GIN,但在您的具体情况下,您需要实际比较 polled 键中包含的一些值。

也许带有expression 的特定INDEX(虽然不是GIN!)可能会有一些用处,但我对此表示怀疑,而且它可能会变得非常麻烦,因为你会至少需要一个 double 类型转换来获取整数值和一个自定义 IMMUTABLE 函数来实际执行 CREATE INDEX 语句中的类型转换。

在采用仅解决某些特定情况的复杂路线之前(如果您需要与不同的fields 键进行另一个比较怎么办?),您可以尝试优化当前查询,利用 PostgreSQL 9.4 新的@ 987654323@ 功能和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 来限制行数,结果证明你真的可以使用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

【讨论】:

Re:“我期待什么样的INDEX”:我不知道——这是我问题的症结所在 :-) 所以我不能真正给出“它的定义” ”。抱歉,如果我不清楚:我正在寻找一种索引解决方案,它支持对 any 字段键的这种查询(不仅仅是polled)。有大量的字段键,并且它们是不可预测的,因此需要每个字段键一个索引的解决方案将无法工作。我喜欢LATERAL 连接方法,但它仍然没有解决索引问题(即,据我所知,您编写的查询需要表扫描)。 所以我猜你在这里不走运。您根本不能在 any key 上有一个索引,并期望将它与任何运算符一起使用,包括算术运算符。您可以在查询中使用GIN INDEX,首先检查给定key 的存在,然后,对于包含它的所有行,比较实际值。但我不确定这会比LATERAL 加入,很大程度上取决于行数。对于我使用LATERAL 的非常相似的情况,我相信这是目前更快的选择。 我已经更新了我的答案,使用 INDEX 并考虑了一些问题。

以上是关于索引 jsonb 用于字段的数字比较的主要内容,如果未能解决你的问题,请参考以下文章

Lucene字段

为啥这个查询在 JSONB Gin 索引字段上花费了这么长时间?我可以修复它以便它实际使用索引吗?

如何比较 Laravel 中 JSONB 列的字段?

pgsql jsonb的索引

在 Postgres 9.4+ 中索引 JSONB 嵌入式 Ecto2 模型

比较两个变量与生成的数字