Postgresql:查询jsonb列 - 索引并不能让它更快

时间:2018-01-04 09:42:10

标签: postgresql indexing jsonb

Postgresql 9.6中有一个表格,jsonb列上的查询与关系表相比较慢,并且在其上添加GIN索引并不会使其更快。

表:

-- create table
create table dummy_jsonb (
    id serial8,
    data jsonb,
    primary key (id)
);

-- create index
CREATE INDEX dummy_jsonb_data_index ON dummy_jsonb USING gin (data);
-- CREATE INDEX dummy_jsonb_data_index ON dummy_jsonb USING gin (data jsonb_path_ops);

生成数据:

-- generate data,
CREATE OR REPLACE FUNCTION dummy_jsonb_gen_data(n integer) RETURNS integer AS $$
DECLARE
    i integer:=1;
    name varchar;
    create_at varchar;
    json_str varchar;
BEGIN
    WHILE i<=n LOOP
        name:='dummy_' || i::text;
        create_at:=EXTRACT(EPOCH FROM date_trunc('milliseconds', now())) * 1000;
        json_str:='{
                 "name": "' || name || '",
                 "size": ' || i || ',
                 "create_at": ' || create_at || '
               }';

        insert into dummy_jsonb(data) values
        (json_str::jsonb
        );
        i:= i + 1;
    END LOOP;

    return n;
END;
$$ LANGUAGE plpgsql;

-- call function,
select dummy_jsonb_gen_data(1000000);

-- drop function,
DROP FUNCTION IF EXISTS dummy_jsonb_gen_data(integer);

查询:

select * from dummy_jsonb
where data->>'name' like 'dummy_%' and data->>'size' >= '500000'
order by data->>'size' desc
offset 50000 limit 10;

测试结果:

  • 慢速vm上的查询需要1.8秒。
  • 添加或删除索引,不会有所作为。
  • 使用jsonb_path_ops更改为索引杜松子酒,也不会有所作为。

问题:

  • 是否可以更快地进行查询,改进索引还是sql?
  • 如果不是,这意味着,在pg中,关系表在这种情况下更合适吗?
  • 而且,在我的测试中,mongodb表现更好,这是否意味着mongodb更适合此类存储&amp;查询?

2 个答案:

答案 0 :(得分:5)

Quote from the manual

  

jsonb的默认GIN运算符类支持使用顶级键存在运算符??&?|运算符以及路径/值存在运算符@>的查询[...]非默认GIN运算符类jsonb_path_ops仅支持为@>运算符建立索引。

您的查询使用LIKE并与>进行字符串比较(开头可能不正确),这些都不受GIN索引的支持。

但即使(data ->> 'name')上的索引也不会用于条件data->>'name' like 'dummy_%',因为所有行都是如此,因为每个名称都以dummy开头。

您可以在名称上创建常规btree索引:

CREATE INDEX ON dummy_jsonb ( (data ->> 'name') varchar_pattern_ops);

如果条件足够严格,将使用哪个,例如:

where data->>'name' like 'dummy_9549%'

如果您需要查询大小,可以在((data ->> 'size')::int)上创建索引,然后使用以下内容:

where (data->>'size')::int >= 500000

但是,使用limitoffset将始终强制数据库读取所有行,对它们进行排序并限制结果。这永远不会很快。您可能需要阅读this article以获取有关限制/偏移效率不高的更多信息。

JSON是关系世界的一个很好的补充,但只有你适当地使用它。如果您不需要行的动态属性,请使用标准列和数据类型。即使JSON支持Postgres非常好,但这并不意味着应该将它用于一切,只是因为它是当前的炒作。 Postgres仍然是一个关系数据库,应该这样使用。

不相关,但是:您生成测试数据的函数可以简化为单个SQL语句。你可能没有意识到generate_series()这样的事情:

insert into dummy_jsonb(data)
select jsonb_build_object('name', 'dummy_'||i, 
                          'size', i::text, 
                          'created_at', (EXTRACT(EPOCH FROM date_trunc('milliseconds', clock_timestamp())) * 1000)::text)
from generate_series(1,1000000) as t(i);

答案 1 :(得分:1)

虽然btree索引(基于二叉树的标准PostgreSQL索引)能够优化基于排序的查询,例如>= '500000'gin索引,使用倒置index 结构,用于快速查找包含特定元素的数据(它非常用于文本搜索以查找包含给定单词的行),因此(AFAIK)它不能用于您提供的查询。 / p>

PostgreSQL docs on jsonb indexing表示可以应用索引的WHERE条件。正如那里指出的那样,您可以在jsonb列中的特定元素上创建btree索引:WHERE子句中引用的特定元素的索引应该适用于您的查询指示。

另外,如上所述,请考虑您的用例是否确实需要JSON。