通过ts_rank_cd订购时,PostgreSQL全文搜索性能不可接受

时间:2014-07-30 09:27:04

标签: sql postgresql full-text-search

在我的PostgreSQL 9.3数据库中,我有一个名为articles的表。它看起来像这样:

+------------+--------------------------------------------------------------+
|    Name    |                        Information                           |
+------------+--------------------------------------------------------------+
| id         | Auto incrememnt integer ID                                   |
| title      | text                                                         |
| category   | character varying(255) with index                            |
| keywords   | String with title and extra words used for indexing          |
| tsv        | Trigger updates w/ tsvector_update_trigger based on keywords |
+------------+--------------------------------------------------------------+

表格中有更多列,但我认为它们对这个问题并不重要。该表的总大小为94GB,大约29M行。

我正在尝试对23M article行的子集上的关键字搜索运行查询。为此,我使用以下查询:

SELECT title, id FROM articles, plainto_tsquery('dog') AS q 
WHERE (tsv @@ q) AND category = 'animal' 
ORDER BY ts_rank_cd(tsv, q) DESC LIMIT 5

这样做的问题在于,它可以先对每个结果运行ts_rank_cd,然后才能对它们进行排序,因此这个查询非常慢,大约需要2-3分钟。我已经阅读了很多尝试找到解决方案,并建议我将搜索查询包装在另一个查询中,以便排名仅应用于找到的结果,如下所示:

SELECT * FROM (
  SELECT title, id, tsv FROM articles, plainto_tsquery('dog') AS q 
  WHERE (tsv @@ q) AND category = 'animal'
) AS t1
ORDER BY ts_rank_cd(t1.tsv, plainto_tsquery('dog')) DESC LIMIT 5;

但是,由于查询太短,因此子集中有450K结果。所以它仍然需要很长时间,它可能会更快一点,但我需要它基本上是即时的。

问题:我可以做什么任何来保持PostgreSQL中的搜索功能?

将这个逻辑保存在数据库中很好,这意味着我不需要任何额外的服务器或配置Solr或Elasticsearch之类的东西。例如,增加数据库实例容量会有所帮助吗?或者,与将此逻辑转移到专用的Elasticsearch实例相比,成本效率是否有意义。


第一个查询的EXPLAIN响应如下:

Limit  (cost=567539.41..567539.42 rows=5 width=465)
  ->  Sort  (cost=567539.41..567853.33 rows=125568 width=465)
        Sort Key: (ts_rank_cd(articles.tsv, q.q))
        ->  Nested Loop  (cost=1769.27..565453.77 rows=125568 width=465)
              ->  Function Scan on plainto_tsquery q  (cost=0.00..0.01 rows=1 width=32)
              ->  Bitmap Heap Scan on articles  (cost=1769.27..563884.17 rows=125567 width=433)
                    Recheck Cond: (tsv @@ q.q)
                    Filter: ((category)::text = 'animal'::text)
                    ->  Bitmap Index Scan on article_search_idx  (cost=0.00..1737.87 rows=163983 width=0)
                          Index Cond: (tsv @@ q.q)

对于第二个问题:

Aggregate  (cost=565453.77..565453.78 rows=1 width=0)
  ->  Nested Loop  (cost=1769.27..565139.85 rows=125568 width=0)
        ->  Function Scan on plainto_tsquery q  (cost=0.00..0.01 rows=1 width=32)
        ->  Bitmap Heap Scan on articles  (cost=1769.27..563884.17 rows=125567 width=351)
              Recheck Cond: (tsv @@ q.q)
              Filter: ((category)::text = 'animal'::text)
              ->  Bitmap Index Scan on article_search_idx  (cost=0.00..1737.87 rows=163983 width=0)
                    Index Cond: (tsv @@ q.q)

3 个答案:

答案 0 :(得分:2)

你根本无法使用ts_rank_cd上的索引,因为它产生的排名值取决于你的查询。因此,每次运行查询时,必须计算整个结果集的所有排名值,然后才能对结果集进行排序并使其受此值限制。

如果您的搜索逻辑允许您可以通过预先计算每个记录的相关性值来避免此瓶颈,请在其上创建索引,并将其用作排序列,而不是每个查询的覆盖敏感度。

即使你说你不想,我建议你研究一下可以和Postgresql一起工作的搜索引擎,比如Sphinx。默认的BM25排名应该可以正常工作。如果必须(http://sphinxsearch.com/docs/current.html#api-func-setfieldweights),您仍然可以设置列权重。

更新:文档中也说明了这一点:

"排名可能很昂贵,因为它需要查询每个匹配文档的tsvector,这可能是I / O绑定因此很慢。不幸的是,几乎不可能避免,因为实际的查询经常导致大量的匹配。"

请参阅http://www.postgresql.org/docs/8.3/static/textsearch-controls.html

答案 1 :(得分:0)

...也许 如果使用HASH索引,您的类别子句可能会被优化掉 您对tsv的查询可能会使用GIN索引进行优化, 如果你的类别是(相当小的)有限集,也许你应该使用枚举作为类别而不是变化(或至少不使用varchar)。 (我想知道在你的情况下重量是否真的重要)。

SELECT *
FROM (SELECT *,ts_rank_cd(sub.tsv, plainto_tsquery('dog')) AS rank
    FROM (SELECT title,id,tsv FROM articles WHERE category = 'animal')) AS sub, 
    plainto_tsquery('dog') AS q
WHERE (tsv @@ q)
ORDER BY rank DESC LIMIT 5

答案 2 :(得分:0)

您应该对类别列编制索引,并且可以尝试增加此特定查询的工作内存,以避免在类别不是减慢它的情况下进行位图堆扫描:

SET LOCAL work_mem =' 64MB';

如果同时执行查询,这可能会大量增加内存使用量。