在全文搜索查询中优化 ORDER BY

xla*_*ash 8 postgresql performance query full-text-search postgresql-performance

我有一张大桌子,里面entities有大约 1500 万条记录。我想在他们的name.

我在 上有一个全文索引name,用于:gin_ix_entity_full_text_search_name

询问:

 SELECT "entities".*,
         ts_rank(to_tsvector('english', "entities"."name"::text),
         to_tsquery('english', 'hockey'::text)) AS "rank0.48661998202865475"
    FROM "entities" 
         WHERE "entities"."place" = 'f' 
              AND (to_tsvector('english', "entities"."name"::text) @@ to_tsquery('english', 'hockey'::text)) 
         ORDER BY "rank0.48661998202865475" DESC LIMIT 5
Run Code Online (Sandbox Code Playgroud)

持续时间 25,623 毫秒

解释计划
1 限制(成本=12666.89..12666.89 行=5 宽度=3116)
2 -> 排序(成本=12666.89..12670.18 行=6571 宽度=3116)
3 排序键: (ts_rank(to_tsvector('english'::regconfig, (name)::text), '''hockey'''::tsquery))
4 -> 实体上的位图堆扫描(成本 = 124.06..12645.06 行 = 6571 宽度 = 3116)
5 重新检查条件: (to_tsvector('english'::regconfig, (name)::text) @@'''hockey'''::tsquery)
6 过滤器:(不是地方)
7 -> gin_ix_entity_full_text_search_name 上的位图索引扫描(成本=0.00..123.74 行=6625 宽度=0)
8 索引条件: (to_tsvector('english'::regconfig, (name)::text) @@'''hockey'''::tsquery)

我不明白为什么它会两次验证索引条件。(查询计划步骤 4 和 7)。是因为我的布尔条件 ( not place) 吗?如果是这样,我应该将它添加到我的索引中以获得非常快速的查询吗?还是排序条件使它变慢?

EXPLAIN ANALYZE 输出:

  限制(成本=4447.28..4447.29行=5宽=3116)(实际时间=18509.274..18509.282行=5循环=1)
  -> Sort (cost=4447.28..4448.41 rows=2248 width=3116) (实际时间=18509.271..18509.273 rows=5 loops=1)
         排序键:(ts_rank(to_tsvector('english'::regconfig, (name)::text), '''test'''::tsquery))
         排序方式:top-N heapsort 内存:19kB
     ->实体上的位图堆扫描(成本=43.31..4439.82行=2248宽度=3116)(实际时间=119.003..18491.408行=2533循环=1)
           复查条件:(to_tsvector('english'::regconfig, (name)::text)@@'''test'''::tsquery)
           过滤器:(不是地方)
           -> Bitmap Index Scan on gin_ix_entity_full_text_search_name (cost=0.00..43.20 rows=2266 width=0) (actual time=74.093..74.093 rows=2593 loops=1)
                 索引条件:(to_tsvector('english'::regconfig, (name)::text) @@'''test'''::tsquery)
 总运行时间:18509.381 毫秒

这是我的数据库参数。它由 Heroku 在 Amazon 服务上托管。他们将其描述为具有 1.7GB 的 RAM、1 个处理单元和最大 1TB 的 DB。

姓名 | 当前设置
------------------------------+-------------------- -------------------------------------------------- ---------------------
 版本| i486-pc-linux-gnu 上的 PostgreSQL 9.0.7,由 GCC gcc-4.4.real (Ubuntu 4.4.3-4ubuntu5) 4.4.3 编译,32 位
 归档命令 | test -f /etc/postgresql/9.0/main/wal-ed/ARCHIVING_OFF || envdir /etc/postgresql/9.0/resource29857_heroku_com/wal-ed/env wal-e wal-push %p
 归档模式 | 在
 存档超时| 1分钟
 checkpoint_completion_target | 0.7
 checkpoint_segments | 40
 client_min_messages | 注意
 cpu_index_tuple_cost | 0.001
 cpu_operator_cost | 0.0005
 cpu_tuple_cost | 0.003
 Effective_cache_size | 1530000kB
 热备| 在
 lc_collat​​e | en_US.UTF-8
 lc_ctype | en_US.UTF-8
 listen_addresses | *
 log_checkpoints | 在
 log_destination | 系统日志
 log_line_prefix | %u [黄色]
 log_min_duration_statement | 50ms
 log_min_messages | 注意
 logging_collector | 在
 维护工作内存| 64MB
 max_connections | 500
 max_prepared_transactions | 500
 max_stack_depth | 2MB
 max_standby_archive_delay | -1
 max_standby_streaming_delay | -1
 max_wal_senders | 10
 港口| 
 random_page_cost | 2
 server_encoding | UTF8
 共享缓冲区| 415MB
 ssl | 在
 syslog_ident | resource29857_heroku_com
 时区 | 世界标准时间
 wal_buffers | 8MB
 wal_keep_segments | 127
 wal_level | 热备
 work_mem | 100MB
 (39 行)

编辑

看起来ORDER BY是缓慢的部分:

d6ifslbf0ugpu=> EXPLAIN ANALYZE SELECT "entities"."name",
     ts_rank(to_tsvector('english', "entities"."name"::text),
     to_tsquery('english', 'banana'::text)) AS "rank0.48661998202865475"
FROM "entities" 
     WHERE (to_tsvector('english', "entities"."name"::text) @@ to_tsquery('english', 'banana'::text)) 
     LIMIT 5;
                                                                   
QUERY PLAN                                                                         
-----------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=43.31..53.07 rows=5 width=24) (actual time=76.583..103.623 rows=5 loops=1)
->  Bitmap Heap Scan on entities  (cost=43.31..4467.60 rows=2266 width=24) (actual time=76.581..103.613 rows=5 loops=1)
     Recheck Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
     ->  Bitmap Index Scan on gin_ix_entity_full_text_search_name  (cost=0.00..43.20 rows=2266 width=0) (actual time=53.592..53.592 rows=1495 loops=1)
           Index Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
 Total runtime: 103.680 ms
 
Run Code Online (Sandbox Code Playgroud)

对比 与ORDER BY

d6ifslbf0ugpu=> EXPLAIN ANALYZE SELECT "entities"."name",
     ts_rank(to_tsvector('english', "entities"."name"::text),
     to_tsquery('english', 'banana'::text)) AS "rank0.48661998202865475"
FROM "entities" 
     WHERE (to_tsvector('english', "entities"."name"::text) @@ to_tsquery('english', 'banana'::text)) 
     ORDER BY "rank0.48661998202865475" DESC
     LIMIT 5;
                                                              
QUERY PLAN                                                                           
---------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit  (cost=4475.12..4475.13 rows=5 width=24) (actual time=15013.735..15013.741 rows=5 loops=1)
->  Sort  (cost=4475.12..4476.26 rows=2266 width=24) (actual time=15013.732..15013.735 rows=5 loops=1)
     Sort Key: (ts_rank(to_tsvector('english'::regconfig, (name)::text), '''banana'''::tsquery))
     Sort Method:  top-N heapsort  Memory: 17kB
     ->  Bitmap Heap Scan on entities  (cost=43.31..4467.60 rows=2266 width=24) (actual time=0.872..15006.763 rows=1495 loops=1)
           Recheck Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
           ->  Bitmap Index Scan on gin_ix_entity_full_text_search_name  (cost=0.00..43.20 rows=2266 width=0) (actual time=0.549..0.549 rows=1495 loops=1)
                 Index Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
 Total runtime: 15013.805 ms
Run Code Online (Sandbox Code Playgroud)

有点我仍然不明白为什么这会变慢。看起来它从位图堆扫描中获取相同数量的行,但它需要更长的时间?

Erw*_*ter 8

我仍然不明白的是,为什么这会变慢。

对行进行排序将花费一些东西是显而易见的。但为什么这么多?
没有ORDER BY rank0...Postgres 就可以

  • 选择它找到的前 5 行,一旦有 5 行就停止获取行。

    实体上的位图堆扫描...行=5 ...

  • 然后只计算ts_rank()5 行。
在第二种情况下,Postgres 必须

  • 获取所有(根据您的查询计划为 1495)符合条件的行。

    实体上的位图堆扫描...行=1495 ...

  • 计算ts_rank()所有这些。
  • 根据计算出的值对它们进行排序以找到前5个。
试着ORDER BY name看看to_tsquery('english', 'hockey'::text))多余行的计算成本以及获取更多行和排序的剩余量。


kgr*_*ttn 5

位图扫描的工作原理如下:扫描索引以查找与索引条件匹配的元组的位置。位图可能会与其他位图扫描的结果进行逻辑组合,对位使用布尔逻辑。然后按页码顺序访问保存数据的页面,以减少磁盘访问并希望将一些随机读取转换为顺序读取。

位图索引扫描可能需要变得“有损”以适应内存——它会降低其从元组级别到页面级别的准确性。如果您增加 work_mem (至少对于这个查询),您可能会避免这种情况。它无法跳过第 4 行的位图堆扫描或第 6 行的过滤器,但它可能能够跳过第 5 行的重新检查。


kgr*_*ttn 5

由于编辑过的问题看起来目标是选择一些最匹配的比赛,而不是所有匹配的列表,我为此发布了一个单独的答案,因为这是一个相当不同的问题。

您可能需要考虑使用KNN - GiST(对于 K 最近邻 - 广义搜索树)索引,它可以按相似性顺序从索引中直接提取 - 因此您不需要随机读取所有这些堆元组并进行排序他们下来找到K个最佳匹配。

迄今为止,我认为没有人实现了对 tsearch 查询的 KNN-GIST 支持(尽管我确信它可以做到,这只是有人花时间去做的问题),但也许是三元组支持(完成)会为你的应用程序。主要区别在于,trigram 搜索不像 tsearch 那样使用字典来提取词干和同义词;你只会找到完全匹配的单词。

要为您的示例尝试三元组,您可能希望像这样索引“名称”:

CREATE INDEX entities_name_trgm ON entities USING gist (name gist_trgm_ops);
Run Code Online (Sandbox Code Playgroud)

然后你可以这样搜索:

SELECT
    *,
    name <-> 'banana' AS sim
  FROM entities 
  WHERE name % 'banana'
  ORDER BY sim DESC
  LIMIT 5;
Run Code Online (Sandbox Code Playgroud)

请注意使用的运算符和ORDER BY使用“相似性”列的别名。尝试时,我不会偏离这种模式太远。tsvector 上的索引不用于此搜索。

除非您当前的配置出现问题(这很容易使您的整个 VM 因内存过量使用而陷入无望的分页),您可能会非常喜欢它的性能。我不知道它是否具有您想要的行为。