2013-05-21 77 views
1

我有一张大约有750万条记录的表格,我试图根据上述表格实现一个自动完成表单,但是表现非常糟糕。如何加快字符串前缀匹配的窗口查询?

的模式(不相关的字段中省略)如下:

COMPANIES 
--------- 
sid (integer primary key) 
world_hq_sid (integer) 
name (varchar(64)) 
marketing_alias (varchar(64)) 
address_country_code (char(4)) 
address_state (varchar(64)) 
sort_order integer 
search_weight integer 
annual_sales integer 

传入的字段是可选的COUNTRY_CODE和状态,与搜索项一起。我想要的是搜索条件匹配(不区分大小写)名称或marketing_alias的开头。我想得到前十名的成绩,其结果也是国家和国家排在前面的那个国家,然后是只有国家,那么没有哪个国家/国家相匹配。之后,我希望按sort_order排序结果。

另外,我只想要一个匹配每个world_hq_sid。最后,当我根据world_hq_sid获得最佳匹配时,我希望最终结果按照search_weight进行排序。

我正在使用窗口查询来实现world_hq_sid部分。下面是该查询:

SELECT * FROM (
    SELECT ROW_NUMBER() OVER (PARTITION BY world_hq_sid ORDER BY CASE WHEN address_country_code = 'US' AND address_state = 'CA' THEN 2 WHEN address_country_code = 'US' THEN 1 ELSE 0 END desc, sort_order asc) AS r, 
    companies.* 
    FROM companies 
    WHERE ((upper(name) LIKE upper('co%')) OR (upper(marketing_alias) LIKE upper('co%'))) 
) x 
    WHERE x.r = 1 
    ORDER BY CASE WHEN address_country_code = 'US' AND address_state = 'CA' THEN 2 WHEN address_state = 'CA' THEN 1 ELSE 0 END desc, search_weight asc, annual_sales desc 
    LIMIT 10; 

我有ADDRESS_STATE,address_country_code,world_hq_sid,排序顺序,并search_weight正常的B树索引。

我对名字和marketing_alias字段中输入以下指标:

CREATE INDEX companies_alias_pattern_upper_idx ON companies(upper(marketing_alias) varchar_pattern_ops); 
CREATE INDEX companies_name_pattern_upper_idx ON companies(upper(name) varchar_pattern_ops) 

这里是解释当我通过CA作为国家和“合作”作为搜索词

Limit (cost=676523.01..676523.03 rows=10 width=939) (actual time=18695.686..18695.687 rows=10 loops=1) 
-> Sort (cost=676523.01..676526.67 rows=1466 width=939) (actual time=18695.686..18695.687 rows=10 loops=1) 
    Sort Key: x.search_weight, x.annual_sales 
    Sort Method: top-N heapsort Memory: 30kB 
    -> Subquery Scan on x (cost=665492.58..676491.33 rows=1466 width=939) (actual time=18344.715..18546.830 rows=151527 loops=1) 
      Filter: (x.r = 1) 
      Rows Removed by Filter: 20672 
      -> WindowAgg (cost=665492.58..672825.08 rows=293300 width=931) (actual time=18344.710..18511.625 rows=172199 loops=1) 
       -> Sort (cost=665492.58..666225.83 rows=293300 width=931) (actual time=18344.702..18359.145 rows=172199 loops=1) 
         Sort Key: companies.world_hq_sid, (CASE WHEN ((companies.address_state)::text = 'CA'::text) THEN 1 ELSE 0 END), companies.sort_order 
         Sort Method: quicksort Memory: 108613kB 
         -> Bitmap Heap Scan on companies (cost=17236.64..518555.98 rows=293300 width=931) (actual time=1861.665..17999.806 rows=172199 loops=1) 
          Recheck Cond: ((upper((name)::text) ~~ 'CO%'::text) OR (upper((marketing_alias)::text) ~~ 'CO%'::text)) 
          Filter: ((upper((name)::text) ~~ 'CO%'::text) OR (upper((marketing_alias)::text) ~~ 'CO%'::text)) 
          -> BitmapOr (cost=17236.64..17236.64 rows=196219 width=0) (actual time=1829.061..1829.061 rows=0 loops=1) 
            -> Bitmap Index Scan on companies_name_pattern_upper_idx (cost=0.00..8987.98 rows=97772 width=0) (actual time=971.331..971.331 rows=169390 loops=1) 
             Index Cond: ((upper((name)::text) ~>=~ 'CO'::text) AND (upper((name)::text) ~<~ 'CP'::text)) 
            -> Bitmap Index Scan on companies_alias_pattern_upper_idx (cost=0.00..8102.02 rows=98447 width=0) (actual time=857.728..857.728 rows=170616 loops=1) 
             Index Cond: ((upper((marketing_alias)::text) ~>=~ 'CO'::text) AND (upper((marketing_alias)::text) ~<~ 'CP'::text)) 
分析

我把work_mem和shared_buffers碰到了100M。

正如你所看到的,这个查询在18秒内返回。奇怪的是,结果对于不同的起始字符是全面的,从400ms(可接受)到30秒(非常不可接受)。 Postgres gurus,我的问题是,我只是期望太多的postgresql快速执行这样的查询?有什么方法可以加快速度?

+0

它的使用,它们会减慢下来的东西'case'表达两类。你可以在这两个表达式上创建一个表达式索引(主要是'CASE WHEN address_state ='CA'...'部分,但是我猜每个语句在窗口集合和整体结果中会有不同的表达式,所以它是可能无法创建一个支持'by by'表达式的索引 –

+0

CASE语句应该在聚合函数和窗口函数上完全相同。编辑问题。但是,整体ORDER BY子句不同。 – duckofdeath

+0

Duck ,我需要看一些你想解决的真实世界用例的更多的说明,我怀疑你可能需要一个非常不同的方法来解决这个问题,现在它看起来像“我试图去划桨从凤凰城到洛杉矶的独木舟,这是非常缓慢的。“ – FuzzyChef

回答

0

对于自动填充,可以使用三字符搜索。

pg_trgm module

CREATE EXTENSION pg_trgm; 
ALTER TABLE companies ADD COLUMN name_trgm TEXT NULL; 
UPDATE companies SET name_trgm = UPPER(name); 

CREATE INDEX companies_name_trgm_gin_idx ON companies USING GIN (name_trgm gin_trgm_ops); 
+0

我不认为这是我们想要的。我们想要一个简单的左锚定不区分大小写的搜索。除非我失去了一些东西,这将retu任意子字符串匹配。 – duckofdeath

1
select * 
from (
    select distinct on (world_hq_sid) 
     world_hq_sid, 
     (address_country_code = 'US')::int + (address_state = 'CA')::int address_weight, 
     sort_order, 
     search_weight, annual_sales, 
     sid, name, marketing_alias, 
     address_country_code, address_state 
    from companies 
    where 
     upper(name) LIKE upper('co%') 
     OR upper(marketing_alias) LIKE upper('co%') 
    order by 1, 2 desc, 3 
) s 
order by 
    address_weight desc, 
    search_weight, 
    annual_sales desc 
limit 10 
+0

address_weight是一个不错的技巧,但是,唉,查询仍然是16秒。 – duckofdeath

+0

@duckofdeath从那里,你必须确定查询计划可能会有所不同,并可能建议不同的索引策略。 –