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

bet*_*max 10 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 |
+------------+--------------------------------------------------------------+
Run Code Online (Sandbox Code Playgroud)

表格中有更多列,但我认为它们对这个问题并不重要.该表的总大小为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
Run Code Online (Sandbox Code Playgroud)

这样做的问题在于,它可以先显示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;
Run Code Online (Sandbox Code Playgroud)

但是,因为查询太短,所以子集中有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)
Run Code Online (Sandbox Code Playgroud)

对于第二个查询:

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)
Run Code Online (Sandbox Code Playgroud)

小智 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
Run Code Online (Sandbox Code Playgroud)