随着搜索字符串变长,Trigram 搜索变得更慢

P.P*_*ter 17 postgresql full-text-search pattern-matching postgresql-9.1 postgresql-9.4

在 Postgres 9.1 数据库中,我有一个table1包含约 1.5M 行和一列的表label(为了这个问题而简化了名称)。

上有一个功能性的三元组索引lower(unaccent(label))(unaccent()已被设为不可变以允许其在索引中使用)。

以下查询非常快:

SELECT count(*) FROM table1
WHERE (lower(unaccent(label)) like lower(unaccent('%someword%')));
 count 
-------
     1
(1 row)

Time: 394,295 ms
Run Code Online (Sandbox Code Playgroud)

但以下查询速度较慢:

SELECT count(*) FROM table1
WHERE (lower(unaccent(label)) like lower(unaccent('%someword and some more%')));
 count 
-------
     1
(1 row)

Time: 1405,749 ms
Run Code Online (Sandbox Code Playgroud)

添加更多单词甚至更慢,即使搜索更严格。

我尝试了一个简单的技巧来运行第一个单词的子查询,然后使用完整的搜索字符串进行查询,但是(可悲的是)查询计划员看穿了我的阴谋:

EXPLAIN ANALYZE
SELECT * FROM (
   SELECT id, title, label from table1
   WHERE lower(unaccent(label)) like lower(unaccent('%someword%'))
   ) t1
WHERE lower(unaccent(label)) like lower(unaccent('%someword and some more%'));
Run Code Online (Sandbox Code Playgroud)
表1上的位图堆扫描(成本=16216.01..16220.04行=1宽度=212)(实际时间=1824.017..1824.019行=1循环=1)
  重新检查条件:((lower(unaccent((label)::text)) ~~ '%someword%'::text) AND (lower(unaccent((label)::text)) ~~ '%someword 等等%'::文本))
  -> 位图索引扫描 table1_label_hun_gin_trgm (cost=0.00..16216.01 rows=1 width=0) (实际时间=1823.900..1823.900 rows=1 loops=1)
        索引条件:((lower(unaccent((label)::text)) ~~ '%someword%'::text) AND (lower(unaccent((label)::text)) ~~ '%someword and some more %'::文本))
总运行时间:1824.064 毫秒

我的最终问题是搜索字符串来自一个网络界面,它可能会发送很长的字符串,因此速度很慢,也可能构成一个 DOS 向量。

所以我的问题是:

  • 如何加快查询速度?
  • 有没有办法将它分解成子查询,使其更快?
  • 也许更高版本的 Postgres 更好?(我尝试了 9.4,但它似乎没有更快:仍然是相同的效果。也许是更高版本?)
  • 也许需要不同的索引策略?

jja*_*nes 35

在 PostgreSQL 9.6 中将会有一个新版本的 pg_trgm,1.2,在这方面会好很多。稍加努力,您也可以让这个新版本在 PostgreSQL 9.4 下工作(您必须应用补丁,并自己编译扩展模块并安装它)。

最旧的版本所做的是搜索查询中的每个三元组并取它们的并集,然后应用过滤器。新版本将做的是选择查询中最稀有的三元组并仅搜索该三元组,然后稍后过滤其余部分。

9.1 中不存在执行此操作的机制。在 9.4 中添加了该机制,但当时 pg_trgm 并未适应使用它。

您仍然会遇到潜在的 DOS 问题,因为恶意人员可以制作只有常见三元组的查询。像“%and%”,甚至“%a%”


如果你不能升级到 pg_trgm 1.2,那么另一种欺骗计划者的方法是:

WHERE (lower(unaccent(label)) like lower(unaccent('%someword%'))) 
AND   (lower(unaccent(label||'')) like 
      lower(unaccent('%someword and some more%')));
Run Code Online (Sandbox Code Playgroud)

通过将空字符串连接到标签,您可以欺骗规划器认为它不能在 where 子句的那部分使用索引。所以它只在 %someword% 上使用索引,并只对那些行应用过滤器。


此外,如果您总是搜索整个单词,则可以使用函数将字符串标记为单词数组,并在该数组返回函数上使用常规的内置 GIN 索引(而不是 pg_trgm)。

  • 值得一提的是,您是编写补丁的人。初步性能测试令人印象深刻。这真的 *** 值得更多赞成***(也用于当前版本的解释和解决方法)。 (13认同)