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


8

我有一张entities约有1500万条记录的大桌子。我想在其中找到与“曲棍球”匹配的前5行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

持续时间25,623 ms

解释计划
1个限制(cost = 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->对实体进行位图堆扫描(cost = 124.06..12645.06行= 6571宽度= 3116)
5重新检查条件:(to_tsvector('english':: regconfig,(name):: text)@@'''hockey''':: tsquery)
6个过滤器:(不放置)
7->在gin_ix_entity_full_text_search_name上进行位图索引扫描(cost = 0.00..123.74 rows = 6625 width = 0)
8索引条件:(to_tsvector('english':: regconfig,(name):: text)@@'''hockey''':: tsquery)

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

EXPLAIN ANALYZE 输出:

  限制(cost = 4447.28..4447.29行= 5宽度= 3116)(实际时间= 18509.274..18509.282行= 5循环= 1)
  ->排序(成本= 4447.28..4448.41行= 2248宽度= 3116)(实际时间= 18509.271..18509.273行= 5循环= 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)
           过滤条件:(未放置)
           ->在gin_ix_entity_full_text_search_name上进行位图索引扫描(成本= 0.00..43.20行= 2266宽度= 0)(实际时间= 74.093..74.093行= 2593循环= 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位编译
 archive_command | 测试-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
 archive_mode | 上
 archive_timeout | 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
 有效缓存大小| 1530000kB
 hot_standby | 上
 lc_collat​​e | zh_CN.UTF-8
 lc_ctype | zh_CN.UTF-8
 listen_addresses | *
 log_checkpoints | 上
 log_destination | 系统日志
 log_line_prefix | %u [黄色]
 log_min_duration_statement | 50毫秒
 log_min_messages | 注意
 logging_collector | 上
 maintenance_work_mem | 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
 shared_buffers | 415兆字节
 ssl | 上
 syslog_ident | resource29857_heroku_com
 时区| 世界标准时间
 wal_buffers | 8MB
 wal_keep_segments | 127
 wal_level | hot_standby
 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

VS. 与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

有点我还是不明白为什么这样慢。看起来它正在从“位图堆扫描”中获取相同数量的行,但是花费的时间更长?


如果增加work_mem不能提供足够的性能提升,请显示EXPLAIN ANALYZE的结果,而不仅仅是EXPLAIN。它还有助于在此页面上显示运行查询的结果:wiki.postgresql.org/wiki/Server_Configuration 硬件的简短描述也有帮助。
kgrittn 2012年

这是另一个解释分析:(请参阅我上面的帖子中的编辑)
xlash 2012年

我应该指出,您显示的PostgreSQL配置不太可能接近最佳配置。但是,这已经足够离题了,应该在一个单独的问题中解决它。我建议您这样做。
kgrittn 2012年

如果你无法理解你的EXPLAIN ANALYZE输出,还有一个Wiki页面,应该帮助:wiki.postgresql.org/wiki/Using_EXPLAIN很多人找到explain.depesz.com是否有帮助; 您可能想在帮助中四处寻找并尝试一下。
kgrittn 2012年

Answers:


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))多余行的计算成本,以及获取更多行和进行排序还剩下多少。


缓存是一种方式...大概会使性能下降。10secs 1500行。我了解您的解释。这说得通。但是,在进行文本搜索时......在不提取所有内容的情况下建立索引以进行正确的质量排序的任何方法?
xlash 2012年

5

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

位图索引扫描可能需要变得“有损”以适合内存-它将其精度从元组级别降低到页面级别。如果增加work_mem(至少针对此查询),则可以避免这种情况。它无法跳过第4行的位图堆扫描或第6行的过滤器,但是它可能能够跳过第5行的重新检查。


1
从100MB增加到650MB,没有任何性能差异。(work_mem)
xlash 2012年

5

由于编辑后的问题看起来像是目标,就是选择一些最匹配的问题,而不是列出所有匹配的列表,因此我为此发布了单独的答案,因为这是一个完全不同的问题。

您可能需要考虑一个KNN - GiST(用于K最近邻居-广义搜索树)索引,该索引可以按相似的顺序从索引中直接拉出-因此您无需随机读取所有这些堆元组并进行排序他们找到K个最佳比赛。

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

为了为您的示例尝试三字母组合,您可能希望像这样对“名称”建立索引:

CREATE INDEX entities_name_trgm ON entities USING gist (name gist_trgm_ops);

然后,您可以像这样搜索:

SELECT
    *,
    name <-> 'banana' AS sim
  FROM entities 
  WHERE name % 'banana'
  ORDER BY sim DESC
  LIMIT 5;

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

除非您当前的配置存在问题(这很容易使整个VM过度使用内存而无法进行分页),否则您可能会非常喜欢这种性能。我是否不知道它是否具有您想要的行为。

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.