多列上的卦索引是否会使搜索更快以及如何正确进行此类搜索?

Will trigram index on multiple columns make search faster and how to make such search properly?

假设我有 table 多列。例如:

id int
name text
surname text
cars json

示例记录为

+----+------+---------+------------------------------------+
| id | name | surname |              cars                  |
+----+------+---------+------------------------------------+
|  1 | John | Doe     | {"values":["Ford", "BMW", "Fiat"]} |
+----+------+---------+------------------------------------+

我想像这样搜索所有这些 table 数据的相关性:

select *,
       similarity(
          'Malcolm Joe likes Ferrary, but hates BMW',
          (name || (cars ->> 'values') || surname)
       ) sim
from public.test_table
where similarity(
         'Malcolm Joe likes Ferrary, but hates BMW',
         (name || (cars ->> 'values') || surname)
      ) > 0.05
order by sim desc;

有什么方法可以加快搜索速度吗?创建三元组索引?如果是这样-如何更好地创建它?在一列上,在每一列上,在串联表达式上?另外,我不明白哪种类型的索引更好——GIN 或 GiST。我读过 GIN 通常更适合常规全文搜索,但 GiST 更适合三元组搜索。对吗?

我也想问下上面的查询有没有更好的写法?

如果有人想知道我为什么选择 trigram,而不是常规的全文搜索 - 这是因为搜索字符串将来自处理一些用户输入,所以当英文 'o' 或 'c' 被西里尔字母取代。我的数据库记录或搜索也可以包含字母数字数据,也可以用三元组更好地处理。

在这种情况下,您需要一个 GiST 索引,因为只有它可以与使用三元组距离运算符的 ORDER BY 查询一起使用:

CREATE INDEX ON public.test_table USING gist
   ((name || (cars ->> 'values') || surname) gist_trgm_ops);

然后应将查询重写为:

SELECT *,
       similarity(
          'Malcolm Joe likes Ferrary, but hates BMW',
          (name || (cars ->> 'values') || surname)
       ) sim
FROM public.test_table
WHERE ((name || (cars ->> 'values') || surname)
       <->
       'Malcolm Joe likes Ferrary, but hates BMW')
      < 0.95
ORDER BY (name || (cars ->> 'values') || surname)
         <->   /* trigram distance */
         'Malcolm Joe likes Ferrary, but hates BMW'
LIMIT 50;

必须重写查询,因为 <-> 有索引支持,但 ORDER BY 表达式中的 similarity() 没有索引支持。

我添加了LIMIT来提示优化器,适当地设置一个限制。

认为 一般而言,GIN 索引对大型表表现更好,但我不确定。无论如何,您对此查询别无选择,因为 GIN 索引不支持该 ORDER BY 子句。

根据您的示例,您可能希望在表达式 (name || (cars ->> 'values') || surname) 上创建索引。但是,您的示例本身没有意义。它是有效的 SQL,但您到底为什么要这样做呢?你为什么要将一个英文句子与一个由某人的全名组成但中间注入了 JSON 的字符串进行比较?这很重要,因为您的示例只有一行,所以索引无关紧要。因此,我们必须将您的示例外推到大量行,其中索引很重要。但由于它在现实世界中没有任何意义,我们如何才能以一种合理的方式对其进行推断?

Also, I haven't understand which type of index is better - GIN or GiST. I've read that GIN is usually better for regular full text search, but GiST is better for trigram search. Is that correct?

根据我的经验,通常情况并非如此。 GiST trigram 索引是基于签名的,其​​中每个 trigram 在签名中设置一个位。但是 trigram 的数量远远多于 bit 的数量,因此它们严重超载。这些类型的索引仅在填充较少时表现良好。 (但是很难提前说 "lightly populated" 是什么意思,其他的 "try it with your real dataset and see"。)鉴于它们的不可预测性,我避免使用 GiST 索引,除非它们有明显的好处,我没有看到这里。

鉴于您的查询,您可以使用任何一种索引,但必须以不同的方式编写。此外,这两个索引是否有帮助是值得怀疑的,因为在

similarity(x,exp) > 0.05

0.05 的截止值非常宽松,索引可能会拒绝很少的行。

如果你有一个更高的截止值,比如 0.5,那么使用 GIN 索引可以将其公式化为:

set pg_trgm.similarity_threshold = 0.5;
select ... from test_table where x % exp order by x <-> exp ;

这将提取足够相似的所有内容,然后按距离对它们进行排序。如果 "sufficiently similar" 足够少,这会提供相当不错的性能(如果不是,您应该重新选择 pg_trgm.similarity_threshold)。正如 Laurenz Albe 所说,使用 GiST 索引,您可以按顺序提取行,然后在达到 LIMIT 时停止,但在没有 LIMIT 子句的情况下,这是没有价值的。