索引 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 用于字段的数字比较的主要内容,如果未能解决你的问题,请参考以下文章
为啥这个查询在 JSONB Gin 索引字段上花费了这么长时间?我可以修复它以便它实际使用索引吗?