全文搜索速度慢,出现频率高


8

我有一个表,其中包含从文本文档中提取的数据。数据存储在名为"CONTENT"GIN 的列中,为此我创建了该索引:

CREATE INDEX "File_contentIndex"
  ON "File"
  USING gin
  (setweight(to_tsvector('english'::regconfig
           , COALESCE("CONTENT", ''::character varying)::text), 'C'::"char"));

我使用以下查询在表上执行全文搜索:

SELECT "ITEMID",
  ts_rank(setweight(to_tsvector('english', coalesce("CONTENT",'')), 'C') , 
  plainto_tsquery('english', 'searchTerm')) AS "RANK"
FROM "File"
WHERE setweight(to_tsvector('english', coalesce("CONTENT",'')), 'C') 
  @@ plainto_tsquery('english', 'searchTerm')
ORDER BY "RANK" DESC
LIMIT 5;

“文件”表包含25万行,每个"CONTENT"条目均包含一个随机词和一个文本字符串,所有行均相同。

现在,当我搜索一个随机单词(整个表中有1个匹配项)时,查询运行非常快(<100毫秒)。但是,当我搜索所有行中都存在的单词时,查询运行非常慢(10分钟或更长时间)。

EXPLAIN ANALYZE显示对于1命中搜索,先执行位图索引扫描,再执行位图堆扫描。对于慢速搜索,将执行Seq扫描,这花费了很长时间。

当然,在所有行中都有相同的数据是不现实的。但是,由于我无法控制用户上载的文本文档,也无法控制用户执行的搜索,因此可能会出现类似的情况(搜索数据库中出现率很高的术语)。在这种情况下,如何提高搜索查询的性能?

运行PostgreSQL 9.3.4

查询计划EXPLAIN ANALYZE

快速搜索(在数据库中有1个匹配项)

"Limit  (cost=2802.89..2802.90 rows=5 width=26) (actual time=0.037..0.037 rows=1 loops=1)"
"  ->  Sort  (cost=2802.89..2806.15 rows=1305 width=26) (actual time=0.037..0.037 rows=1 loops=1)"
"        Sort Key: (ts_rank(setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char"), '''wfecg'''::tsquery))"
"        Sort Method: quicksort  Memory: 25kB"
"        ->  Bitmap Heap Scan on "File"  (cost=38.12..2781.21 rows=1305 width=26) (actual time=0.030..0.031 rows=1 loops=1)"
"              Recheck Cond: (setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char") @@ '''wfecg'''::tsquery)"
"              ->  Bitmap Index Scan on "File_contentIndex"  (cost=0.00..37.79 rows=1305 width=0) (actual time=0.012..0.012 rows=1 loops=1)"
"                    Index Cond: (setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char") @@ '''wfecg'''::tsquery)"
"Total runtime: 0.069 ms"

搜索速度慢(数据库中有25万次匹配)

"Limit  (cost=14876.82..14876.84 rows=5 width=26) (actual time=519667.404..519667.405 rows=5 loops=1)"
"  ->  Sort  (cost=14876.82..15529.37 rows=261017 width=26) (actual time=519667.402..519667.402 rows=5 loops=1)"
"        Sort Key: (ts_rank(setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char"), '''cyberspace'''::tsquery))"
"        Sort Method: top-N heapsort  Memory: 25kB"
"        ->  Seq Scan on "File"  (cost=0.00..10541.43 rows=261017 width=26) (actual time=2.097..519465.953 rows=261011 loops=1)"
"              Filter: (setweight(to_tsvector('english'::regconfig, (COALESCE("CONTENT", ''::character varying))::text), 'C'::"char") @@ '''cyberspace'''::tsquery)"
"              Rows Removed by Filter: 6"
"Total runtime: 519667.429 ms"

1
从我的头开始:GIN索引在Postgres 9.4(以及即将发布的9.5)中得到了重大改进。升级到当前的9.4版本肯定会付出代价。我还将研究GiST的性能,而不是GIN指数。您查询的罪魁祸首是ORDER BY "RANK" DESC。我将pg_trgm使用GiST索引和相似性/距离运算符作为替代进行调查。考虑:dba.stackexchange.com/questions/56224/…。甚至可能产生“更好”的结果(速度更快)。
Erwin Brandstetter 2015年

您在什么操作系统上运行PostgreSQL实例?
卡萨德里(Kassandry)2015年

您是否可以重复这些操作explain (analyze, buffers),最好将track_io_timing设置为ON?除非您将其存储在软盘的RAID中,否则没有必要花费520秒来对表进行顺序扫描。那里肯定是病态的。另外,您对的设置random_page_cost以及其他费用参数?
jjanes

@danjo即使不使用订购,我也面临相同的问题。你能告诉我如何解决吗?
萨希尔·巴尔

Answers:


11

可疑的用例

...每个CONTENT条目均由一个随机词和一个文本字符串组成,所有行均相同。

所有行都相同的文本字符串就是固定运费。如果需要显示它,请将其删除并连接到视图中。

显然,您已经知道:

当然,这是不现实的...但是由于我无法控制文字...

升级您的Postgres版本

运行PostgreSQL 9.3.4

仍在使用Postgres 9.3时,至少应升级到最新的点发行版(当前为9.3.9)。该项目的官方建议:

我们始终建议所有用户针对正在使用的主要版本运行最新的次要版本。

更好的是,升级到9.4,这已对GIN索引进行重大改进

主要问题1:费用估算

直到9.4版(包括9.4版),一些文本搜索功能的成本都被严重低估了。在即将到来的9.5版本中,该成本增加了100倍,就像@jjanes在他最近的回答中描述的那样:

这里是讨论该问题的各个线程以及Tom Lane 的提交消息

正如您在提交消息中看到的那样,to_tsvector()这些功能就是其中之一。您可以立即(以超级用户身份)应用更改:

ALTER FUNCTION to_tsvector (regconfig, text) COST 100;

这应该让很多更可能是你的功能使用索引。

主要问题2:KNN

核心问题是Postgres必须先计算ts_rank()260k行(rows=261011)的等级,然后才能排序并选择前5名。即使您已经解决了所讨论的其他问题,这也将非常昂贵。从本质上讲,这是一个K近邻(KNN)问题,并且有相关案例的解决方案。但是我无法为您的情况想到一个通用的解决方案,因为排名计算本身取决于用户的输入。我会尽力消除低排名比赛的大部分,以便只对少数优秀候选人进行完整的计算。

想到的一种方法是将全文搜索与三字母组相似度搜索结合在一起 -这为KNN问题提供了可行的实现。这样,您可以预先选择LIKE谓词为候选的“最佳”匹配项(LIMIT 50例如,在带有子查询的情况下),然后根据主查询中的排名计算来选择5个排名最高的行。

或在同一查询中应用两个谓词,然后根据三字组相似度(会产生不同的结果)选择最接近的匹配项,例如此相关答案:

我进行了更多研究,您不是第一个遇到此问题的人。pgsql-general上的相关文章:

最终实施tsvector <-> tsquery操作员的工作正在进行中。

Oleg Bartunov和Alexander Korotkov甚至提出了一个可行的原型(使用><运算符代替了<->当时的原型),但集成到Postgres中非常复杂,GIN索引的整个基础结构都必须重新设计(其中大部分现在已经完成)。

主要问题3:权重和指标

而且,我发现了导致查询缓慢的另一个因素。每个文档:

对于标准查询,GIN索引不是有损的,但是其性能在对数上取决于唯一单词的数量。(但是,GIN索引仅存储值的单词(词法)tsvector,而不存储其权重标签。因此,在使用涉及权重的查询时,需要对表行进行重新检查。)

大胆强调我的。一旦涉及重量,就必须从堆中提取每一行(而不仅仅是廉价的可见性检查),并且必须删除长值,这增加了成本。但是似乎有一个解决方案:

索引定义

再次查看索引,开始似乎没有任何意义。您将权重分配给单个列,只要您不将权重不同的其他列连接起来,就没有意义

COALESCE() 只要您实际上不连接更多的列,也没有任何意义。

简化索引:

CREATE INDEX "File_contentIndex" ON "File" USING gin
(to_tsvector('english', "CONTENT");

和您的查询:

SELECT "ITEMID", ts_rank(to_tsvector('english', "CONTENT")
                       , plainto_tsquery('english', 'searchTerm')) AS rank
FROM   "File"
WHERE  to_tsvector('english', "CONTENT")
       @@ plainto_tsquery('english', 'searchTerm')
ORDER  BY rank DESC
LIMIT  5;

匹配每一行的搜索词仍然很昂贵,但可能少得多。

阿西德斯

所有这些问题加在一起,第二个查询的520秒的疯狂花费开始变得有意义。但是仍然可能存在更多问题。您是否配置了服务器?
适用于性能优化的所有常规建议均适用。

如果您不使用双引号CaMeL-case标识符,它将使您的生活更加轻松:


我也遇到了这个问题。在Postgresql 9.6中,我们对同义词使用查询重写,因此我不认为使用三字母组相似度搜索来限制文档数量会很好。
pholly

惊人!USING gin (to_tsvector('english', "CONTENT")
K-Gun

1

我有类似的问题。我通过针对field:table元组预先计算每个流行的文本查询词的ts_rank并将其存储在查找表中来解决此问题。在文本重语料库中搜索流行词期间,这为我节省了很多时间(40倍)。

  1. 扫描文档并计算其出现次数,即可获得语料库中的常用单词。
  2. 按最受欢迎的词排序。
  3. 预计算流行词ts_rank并将其存储在表中。

查询:查找该表并获取按其各自等级排序的文档ID。如果不存在,请使用旧方法。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.