优化postgres搜索查询的问题

时间:2014-05-29 06:08:59

标签: sql postgresql query-optimization top-n

我遇到以下PostgreSQL查询的问题,运行需要10秒以上是否有任何方法可以将此查询加速到合理的速度,我只是在寻找与视频相关的最相关的搜索字词非常大的数据库。

  SELECT count(*), videoid 
  FROM term_search 
  where word = 'tester' 
     OR word = 'question' 
     OR word = 'one' 
  group by videoid 
  order by count(*) desc 
  limit 1800;

使用analyze运行查询时,生成的查询计划如下(http://explain.depesz.com/s/yDJ):

  Limit  (cost=389625.50..389630.00 rows=1800 width=4) (actual time=11766.693..11770.001 rows=1800 loops=1)
     Output: (count(*)), videoid
     ->  Sort  (cost=389625.50..389692.68 rows=26873 width=4) (actual time=11766.689..11767.818 rows=1800 loops=1)
           Output: (count(*)), videoid
           Sort Key: (count(*))
           Sort Method: top-N heapsort  Memory: 181kB
           ->  HashAggregate  (cost=387769.41..388038.14 rows=26873 width=4) (actual time=9215.653..10641.993 rows=1632578 loops=1)
                 Output: count(*), videoid
                 ->  Bitmap Heap Scan on public.term_search  (cost=44915.83..378163.38 rows=1921207 width=4) (actual time=312.449..7026.036 rows=2047691 loops=1)
                       Output: id, videoid, word, termindex, weight
                       Recheck Cond: (((term_search.word)::text = 'tester'::text) OR ((term_search.word)::text = 'question'::text) OR ((term_search.word)::text = 'one'::text))
                       Rows Removed by Index Recheck: 25512434
                       ->  BitmapOr  (cost=44915.83..44915.83 rows=1950031 width=0) (actual time=288.937..288.937 rows=0 loops=1)
                             ->  Bitmap Index Scan on terms_word_idx  (cost=0.00..8552.83 rows=383502 width=0) (actual time=89.266..89.266 rows=419750 loops=1)
                                   Index Cond: ((term_search.word)::text = 'tester'::text)
                             ->  Bitmap Index Scan on terms_word_idx  (cost=0.00..13171.84 rows=590836 width=0) (actual time=89.700..89.700 rows=604348 loops=1)
                                   Index Cond: ((term_search.word)::text = 'question'::text)
                             ->  Bitmap Index Scan on terms_word_idx  (cost=0.00..21750.26 rows=975693 width=0) (actual time=109.964..109.964 rows=1023593 loops=1)
                                   Index Cond: ((term_search.word)::text = 'one'::text)

该表的架构如下:

    Column   |          Type          |                        Modifiers                         | Storage  | Description 
  -----------+------------------------+----------------------------------------------------------+----------+-------------
   id        | integer                | not null default nextval('term_search_id_seq'::regclass) | plain    | 
   videoid   | integer                |                                                          | plain    | 
   word      | character varying(100) |                                                          | extended | 
   termindex | character varying(15)  |                                                          | extended | 
   weight    | smallint               |                                                          | plain    | 
  Indexes:
      "term_search_pkey" PRIMARY KEY, btree (id)
      "search_term_exists_idx" btree (videoid, word)
      "terms_caverphone_idx" btree (termindex)
      "terms_video_idx" btree (videoid)
      "terms_word_idx" btree (word, videoid)
  Foreign-key constraints:
      "term_search_videoid_fkey" FOREIGN KEY (videoid) REFERENCES videos(id) ON DELETE CASCADE
  Has OIDs: no

我已经设法通过Index Only扫描将其降低到7秒,但仍然不够低。我在一个aws r3.xlarge实例上在Ubuntu 14.04上运行PostgreSQL 9.3,表中有大约5000万行。任何意见是极大的赞赏!

编辑:

附加是SELECT schemaname,tablename,attname,null_frac,avg_width,n_distinct FROM pg_stats WHERE schemaname =' public'和tablename =' term_search';

 schemaname |  tablename  |  attname  | null_frac | avg_width | n_distinct 
 ------------+-------------+-----------+-----------+-----------+------------
 public     | term_search | id        |         0 |         4 |         -1
 public     | term_search | videoid   |         0 |         4 |     568632
 public     | term_search | word      |         0 |         6 |       5054
 public     | term_search | termindex |         0 |        11 |       2485
 public     | term_search | weight    |         0 |         2 |          3

4 个答案:

答案 0 :(得分:1)

如果我有机会连接用户一晚,我会:

  • words
  • 创建一个包含term_search的新表格
  • 创建对新表的引用,
  • 删除列word

类似的东西:

create table words (
    word_id serial primary key,
    word text);

insert into words (word)
    select distinct word
    from term_search;

alter table term_search add column word_id integer;

update term_search t
    set word_id = w.word_id
    from words w
    where t.word = w.word;

alter table term_search add constraint term_search_word_fkey 
    foreign key (word_id) references words (word_id);

测试:

SELECT count(*), videoid 
FROM term_search t
JOIN words w on t.word_id = w.word_id
WHERE w.word = 'tester' 
   OR w.word = 'question' 
   OR w.word = 'one' 
GROUP BY videoid 
ORDER BY count(*) desc 
LIMIT 1800;    

-- if was faster then
    alter table term_search drop column word;
-- and on the fly...
    alter table term_search alter termindex type text;

革命后,我必须处理term_search上的插入和更新。我可能会创建一个包含插入和更新规则的视图。

答案 1 :(得分:1)

让我们从重新描述查询开始,解释它真正想做的事情。

查询:

  SELECT count(*), videoid 
  FROM term_search 
  where word = 'tester' 
     OR word = 'question' 
     OR word = 'one' 
  group by videoid 
  order by count(*) desc 
  limit 1800;

似乎意味着:

“在搜索字词表中,找到包含搜索字词testerquestionone的视频。计算每个视频的匹配项并最多返回1800个视频匹配”。

或者更一般地说:

“找到最适合我搜索字词的视频,并向我展示前n个最佳匹配”。

正确?

如果是这样,为什么不使用PostgreSQL's built-in full-text search and full-text indexing?与每个视频tsquery进行索引tsvector匹配可能会在此获胜。全文搜索具有模糊匹配,排名以及几乎所有其他您想要的东西 - 与您当前的方法不同,它不需要实现整个数据集并仅排序以丢弃大部分数据集。

您尚未提供样本数据,因此我无法真正进行演示。


PostgreSQL当前如何执行您的查询可以这样解释:

  • 为表中的每个磁盘页(8kb)创建一个位,其中true表示页面可能包含一个或多个匹配的行

  • 对于每个搜索字词,扫描索引terms_word_idx并更新位图以设置找到匹配项的位

  • 扫描表格,跳过位图显示无法匹配的页面,查找包含任何单词的行。这就像一个快速,跳过空白的seqscan。如果比赛的百分比很高,它实际上并不比普通的seqscan快。

  • 对于每个匹配的行,根据视频ID将其排序为一系列“存储桶”。然后在最后,计算每个存储桶中的行数并返回该存储桶的计数+视频ID。 (这不是那么简单,但足够接近)。

  • 在计算每个存储桶时,将结果放在结果中,包括次高和次最低计数。

    • 取得前1800名的成绩,然后扔掉所有剩下的辛勤工作。

这听起来不是很有趣,但它没有任何选择。 b树索引不能同时搜索多个术语,因此必须进行多个索引扫描。剩下的就是那个。

所以:为了提高效率,你需要从根本上改变解决问题的方法。添加索引或调整一些参数不会突然使这需要0.5秒。

答案 2 :(得分:0)

您可以优化postgresql设置以减少查询执行的时间。例如,您可以使用 pgtune utilite:

apt-get install pgtune
cd /etc/postgresql/*.*/main/
cp postgresql.conf postgresql.conf.default
pgtune -i postgresql.conf.default -o postgresql.conf --type=%TYPE%

此处%TYPE%是值之一:

  • DATA 适用于大数据群发,大型查询,低频呼叫
  • WEB 适用于网络应用程序,最适合Django应用程序和其他WEB应用程序

有关pgtune的其他信息,请访问Google并提供帮助。

对于PostgreSQL< 9.3你必须使用这个脚本:

#!/bin/bash
# simple shmsetup script
page_size=`getconf PAGE_SIZE`
phys_pages=`getconf _PHYS_PAGES`
shmall=`expr $phys_pages / 2`
shmmax=`expr $shmall \* $page_size`
echo kernel.shmmax = $shmmax
echo kernel.shmall = $shmall

白色结果进入文件/etc/sysctl.conf并重启系统。否则Postgres无法启动。

答案 3 :(得分:0)

其他人已就如何重构数据库提供了一些建议,但您可以使查询运行得更好。 EXPLAIN中的以下行表明您的位图溢出:

Rows Removed by Index Recheck: 25512434

如果重新检查耗费时间(而不是IO消耗时间 - 如果你运行EXPLAIN (ANALYZE, BUFFERS)它将有助于澄清这一点,特别是如果你启用了track_io_timing)那么增加work_mem可能会有很大帮助,假设您可以负担得起,而不会耗尽RAM。