1

假设我有一个包含多列的表。例如:

id int
name text
surname text
cars json

示例记录将是

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

我想搜索所有这些表数据的相关性,如下所示:

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 搜索。那是对的吗?

我还想问是否有更好的方法来编写上述查询?

如果有人想知道我为什么选择 trigram 而不是常规的全文搜索 - 这是因为搜索字符串将来自处理一些用户输入,因此可能会出现错误,甚至可能会出现错误,甚至会出现英文“o”或“c”被西里尔字母替换的情况。我的数据库记录或搜索也可以包含字母数字数据,这也可以用三元组更好地处理。

4

2 回答 2

2

在这种情况下,您需要一个 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;

查询必须重写,因为索引支持<->,但不支持similarity()inORDER BY表达式。

我添加了LIMIT提示优化器,并酌情设置限制。

认为通常 GIN 索引对于大型表的性能更好,但我不确定。无论如何,您对这个查询别无选择,因为 GIN 索引不支持该ORDER BY子句。

于 2019-10-11T08:42:00.227 回答
0

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

另外,我不明白哪种类型的索引更好——GIN 或 GiST。我读过 GIN 通常更适合常规全文搜索,但 GiST 更适合 trigram 搜索。那是对的吗?

根据我的经验,通常不是这样。GiST trigram 索引是基于签名的,其​​中每个 trigram 在签名中设置一个位。但是三元组的数量远远多于位,因此它们严重超载。这些类型的索引只有在填充量很少时才能很好地执行。(但很难提前说出“人口稀少”是什么意思,其他他们“用你的真实数据集试试看”。)鉴于它们的不可预测性,我避免使用 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 ;

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

于 2019-10-11T14:51:03.053 回答