【问题标题】:Index for searching nested JSONB array elements in PostgreSQL在 PostgreSQL 中搜索嵌套 JSONB 数组元素的索引
【发布时间】:2017-04-12 10:43:33
【问题描述】:

我对 postgres 还很陌生,目前使用的是 9.6。 当尝试使用它的 jsonb 文档在 postgres 中实现全文搜索时,我注意到嵌套数组的搜索结果很慢。我使用了“解释”命令,它没有使用任何索引。 为简单起见,我创建了一个表格进行调查:

CREATE TABLE book (
  id   BIGSERIAL NOT NULL,
  data JSONB     NOT NULL
);

我的可用索引:

CREATE INDEX book_author_idx
  ON book USING GIN (to_tsvector('english', book.data ->> 'author'));
CREATE INDEX book_author_name_idx
  ON book USING GIN (to_tsvector('english', book.data -> 'author' ->> 'name'));

还有一些数据来填写文档:

INSERT INTO book (data)
VALUES (CAST('{"author": [{"id": 0, "name": "Cats"}, ' ||
             '           {"id": 1, "name": "Dogs"}]}' AS JSONB));

我可以使用以下查询搜索书籍元素,但它不使用任何索引。我的 120k 个产品的实际数据大约需要 1200 毫秒,而其他带有索引的搜索需要 0.2 毫秒。

EXPLAIN ANALYZE
SELECT
  id,
  data ->> 'author' AS author
FROM book, jsonb_array_elements(data #> '{author}') author_array
WHERE to_tsvector('english', author_array ->> 'name') @@ to_tsquery('cat');

相比之下,下一个查询使用 book_author_name_idx 但由于数组结构没有找到任何东西。

EXPLAIN ANALYZE
SELECT
  id,
  data ->> 'author' AS author
FROM book
WHERE to_tsvector('english', data -> 'author' ->> 'name') @@ to_tsquery('cat');

如何调整我的查询以使用语言索引? 我知道,我可以为作者创建一个新表并仅引用 ID,但我宁愿将所有数据保存在一个表中以提高性能。

【问题讨论】:

  • LATERAL JOIN 中使用unnest() 及其朋友(结果集生成函数,如jsonb_array_elements())可防止使用任何索引(至少在从它们计算的属性上)。如果您坚持使用这种结构,则必须创建一个自定义的 IMMUTABLE 函数,以从您的 jsonb 列中生成 tsvector 值,并在索引和查询中使用该函数。
  • 其中有趣的部分是 tsvector 没有任何内置聚合,因此您需要 1) 将名称聚合为字符串(使用一些基本规则) 2) 构建一个tsvector 的自定义聚合 3) 使用巧妙的递归 CTE(因为它们已经存在连接)。

标签: arrays postgresql indexing full-text-search jsonb


【解决方案1】:

根据 posz comments 的提示,我找到了解决方案。 因为'||'函数不能按我需要的方式工作,我为 tsvectors 使用了自定义 concat 函数。我使用了 github 上 glittershark 的代码,并将 to_tsvector 从 'default' 更改为 'english' 以满足我的需要。

CREATE OR REPLACE FUNCTION concat_tsvectors(tsv1 TSVECTOR, tsv2 TSVECTOR)
  RETURNS TSVECTOR AS $$
BEGIN
  RETURN coalesce(tsv1, to_tsvector('english', ''))
         || coalesce(tsv2, to_tsvector('english', ''));
END;
$$ LANGUAGE plpgsql;

CREATE AGGREGATE tsvector_agg (
BASETYPE = TSVECTOR,
SFUNC = concat_tsvectors,
STYPE = TSVECTOR,
INITCOND = ''
);

这是我写的自定义函数。输入是 JSONB 格式的数据,输出是带有聚合作者姓名的 tsvector。

CREATE OR REPLACE FUNCTION author_function(
  IN  data        JSONB,
  OUT resultNames TSVECTOR
)
  RETURNS TSVECTOR AS $$
DECLARE
  authorRecords   RECORD;
  combinedAuthors JSONB [];
  singleAuthor    JSONB;
BEGIN
  FOR authorRecords IN (SELECT value
                        FROM jsonb_array_elements(data #> '{author}'))
  LOOP
    combinedAuthors := combinedAuthors || authorRecords.value;
  END LOOP;
  FOREACH singleAuthor IN ARRAY coalesce(combinedAuthors, '{}')
  LOOP
    resultNames := concat_tsvectors(resultNames, to_tsvector('english', singleAuthor ->> 'name'));
  END LOOP;
END; $$
LANGUAGE plpgsql
IMMUTABLE;

然后我为我的图书对象设置一个索引。

CREATE INDEX book_author_function_idx
  ON book USING GIN (author_function(book.data));

作者姓名已经通过 to_tsvector('english', singleAuthor) 函数,所以我可以这样查询:

EXPLAIN ANALYSE
SELECT
  id,
  data ->> 'author' AS author
FROM book
WHERE author_function(book.data) @@ to_tsquery('cat');

因此,对我的实际数据的查询从 1100-1200 毫秒变为 ~0.5 毫秒。 我不确定这是否是最好的解决方案,所以如果您有更好的建议,请告诉我。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-15
    • 2020-03-25
    • 2017-09-11
    • 2018-05-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多