【问题标题】:Poor regex performance for word matching on postgrespostgres 上单词匹配的正则表达式性能不佳
【发布时间】:2018-06-30 13:08:32
【问题描述】:

我有一个被屏蔽的短语列表,我想匹配这些短语在用户输入的文本中是否存在,但性能很差。

我正在使用这个查询:

SELECT value FROM blocked_items WHERE lower(unaccent( 'my input text' )) ~* ('[[:<:]]' || value || '[[:>:]]') LIMIT 1;

经过调查,我发现世界边界 [[:&lt;:]] 和 [[:&gt;:]] 知道blocked_items 中有24k 条记录,性能非常差。

例如,当我尝试运行这个时:

SELECT value FROM blocked_items WHERE lower(unaccent( 'my input text ' )) ilike ('%' || value || '%') LIMIT 1;

与第一个相比,它非常快。问题是我需要保留单词边界的测试。

在大型程序上经常执行此检查,因此性能对我来说非常重要。

你们有什么建议可以加快这个速度吗?

EXPLIAN ANALYZE screenshot

【问题讨论】:

  • 也许你可以使用Full Text Search?也许效果更好。
  • 您不仅删除了单词边界断言,还将运算符从正则表达式匹配更改为子字符串匹配。当然,做根本不同的事情需要不同的时间。如果我在仍然使用相同的运算符的同时简单地添加或删除单词边界断言,我会在时间上得到微不足道的差异。当然,我必须自己编写正则表达式,因为我不知道您的 blocked_items 表中有什么。
  • 您可以在list_id 列上添加一个btree 索引来减少seqscan 中的行数。

标签: sql regex postgresql postgresql-9.3


【解决方案1】:

既然您知道LIKE (~~) 查询速度快而RegEx (~) 查询速度慢,那么最简单的解决方案是将这两个条件结合起来(\m / \M 是@987654321 @到[[:&lt;:]]/[[:&gt;:]]):

SELECT value FROM blocked_items
WHERE lower(unaccent('my input text')) ~~ ('%'||value||'%')
  AND lower(unaccent('my input text')) ~ ('\m'||value||'\M')
LIMIT 1;

这样,快速查询条件过滤掉大部分行,然后慢速条件丢弃剩余的行。

假设value 已经标准化,我正在使用更快的区分大小写的运算符。如果不是这种情况,请删除(然后是多余的)lower(),并像在原始查询中一样使用区分大小写的版本。

在我的具有 370k 行的测试集上,将查询从 6s(暖)加速到 90ms:

                                                                                       QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.00..1651.85 rows=1 width=10) (actual time=89.702..89.702 rows=1 loops=1)
   ->  Seq Scan on blocked_items  (cost=0.00..14866.61 rows=9 width=10) (actual time=89.701..89.701 rows=1 loops=1)
         Filter: ((lower(unaccent('my input text'::text)) ~~ (('%'::text || value) || '%'::text)) AND (lower(unaccent('my input text'::text)) ~ (('\m'::text || value) || '\M'::text)))
         Rows Removed by Filter: 153281
 Planning Time: 0.097 ms
 Execution Time: 89.717 ms
(6 rows)

但是,我们仍在进行全表扫描,性能会因表中的位置而异。

理想情况下,我们可以使用索引在几乎恒定的时间内回答查询。

让我们重写查询以使用Text Search Functions and Operators:

SELECT value FROM blocked_items
WHERE to_tsvector('simple', unaccent('my input text'))
   @@ phraseto_tsquery('simple', value)
LIMIT 1;

首先我们将输入拆分为search vectors,然后检查被阻止的短语是否与这些向量匹配。

测试查询大约需要 440 毫秒 - 从我们的组合查询中退一步:

                                                             QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.00..104.01 rows=1 width=10) (actual time=437.761..437.761 rows=1 loops=1)
   ->  Seq Scan on blocked_items  (cost=0.00..192516.05 rows=1851 width=10) (actual time=437.760..437.760 rows=1 loops=1)
         Filter: (to_tsvector('simple'::regconfig, unaccent('my input text'::text)) @@ phraseto_tsquery('simple'::regconfig, value))
         Rows Removed by Filter: 153281
 Planning Time: 0.063 ms
 Execution Time: 437.772 ms
(6 rows)

由于我们不能使用tsvector @@ tsquery 来索引tsquery,我们可以再次重写查询以检查被阻止的短语是否是使用tsquery @&gt; tsquery Text Search Operator 的输入短语的子查询,然后可以使用来自GiST Operator Classes 的tsquery_ops 进行索引:

CREATE INDEX blocked_items_search ON blocked_items
  USING gist (phraseto_tsquery('simple', value));

ANALYZE blocked_items; -- update query planner stats

SELECT value FROM blocked_items
WHERE phraseto_tsquery('simple', unaccent('my input text'))
   @> phraseto_tsquery('simple', value)
LIMIT 1;

查询现在可以使用索引扫描,并且需要 20ms 处理相同的数据。

由于 GiST 是有损索引,查询时间可能会根据需要重新检查的次数而有所不同:

                                                                QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.54..4.23 rows=1 width=10) (actual time=19.215..19.215 rows=1 loops=1)
   ->  Index Scan using blocked_items_search on blocked_items  (cost=0.54..1367.01 rows=370 width=10) (actual time=19.214..19.214 rows=1 loops=1)
         Index Cond: (phraseto_tsquery('simple'::regconfig, value) <@ phraseto_tsquery('simple'::regconfig, unaccent('my input text'::text)))
         Rows Removed by Index Recheck: 4028
 Planning Time: 0.093 ms
 Execution Time: 19.236 ms
(6 rows)

使用全文搜索的一大优势是您现在可以通过搜索配置 (regconfig) 使用特定语言的单词匹配。

以上查询都使用默认的'simple' regconfig 来匹配原始查询的行为。通过切换到'english',您还可以匹配同一个词的变体,例如 cat 和 cats (stemming) 以及没有意义的普通词,例如 the em> 或 my 将被忽略 (stop words)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-09-20
    • 1970-01-01
    • 2018-11-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多