2015-12-14 29 views
7

我有以下表:如何提高Postgres select语句的速度?

CREATE TABLE views (
    view_id bigint NOT NULL, 
    usr_id bigint, 
    ip inet, 
    referer_id bigint, 
    country_id integer, 
    validated smallint, 
    completed smallint, 
    value numeric 
); 

ALTER TABLE ONLY views 
    ADD CONSTRAINT "Views_pkey" PRIMARY KEY (view_id); 

CREATE TABLE country (
    country_id integer NOT NULL, 
    country character varying(2) 
); 

ALTER TABLE ONLY country 
    ADD CONSTRAINT country_pkey PRIMARY KEY (country_id); 

CREATE TABLE file_id_view_id (
    file_id bigint, 
    view_id bigint, 
    created_ts timestamp without time zone 
); 

CREATE TABLE file_owner (
    file_id bigint NOT NULL, 
    owner_id bigint 
); 

ALTER TABLE ONLY file_owner 
     ADD CONSTRAINT owner_table_pkey PRIMARY KEY (file_id); 

CREATE TABLE referer (
    referer_id bigint NOT NULL, 
    referer character varying(255) 
); 

ALTER TABLE ONLY referer 
    ADD CONSTRAINT referer_pkey PRIMARY KEY (referer_id); 

viewsfile_id_view_id表具有大约340M每个。每小时他们都会增加600K行。

file_owner表有75K行,将被行每小时增加。

country表有行并且很少发生变化。

referer表有行,很少发生变化。

我的目标是能够执行的查询,例如:

SELECT Count(ft.*)      AS total_views, 
     (Count(ft.*) - SUM(ft.valid)) AS invalid_views, 
     SUM(ft.valid)     AS valid_views, 
     SUM(ft.values)     AS VALUES, 
     ft.day       AS day, 
     (CASE 
      WHEN r.referer IS NULL THEN 'Unknown' 
      ELSE r.referer 
     END)       AS referer, 
     (CASE 
      WHEN c.country IS NULL THEN 'Unknown' 
      ELSE c.country 
     END)       AS country 
FROM country c 
     right join (referer r 
        right join (SELECT v.validated AS valid, 
             v.value  AS VALUES, 
             vf.day  AS day, 
             vf.view_id AS view_id, 
             v.referer_id AS referer_id, 
             v.country_id AS country_id 
           FROM VIEWS v, 
             (SELECT view_id, 
fivi.created_ts :: timestamp :: DATE AS 
day 
FROM file_id_view_id fivi 
join (SELECT file_id 
     FROM file_owner 
     WHERE owner_id = 75 
     GROUP BY file_id) fo 
    ON (fo.file_id = fivi.file_id) 
WHERE (fivi.created_ts BETWEEN 
    '2015-11-01' AND '2015-12-01') 
GROUP BY view_id, 
    day) vf 
WHERE v.view_id = vf.view_id) ft 
ON (ft.referer_id = r.referer_id)) 
ON (ft.country_id = c.country_id) 
GROUP BY day, 
      referer, 
      country; 

为了产生:

GroupAggregate (cost=38893491.99..40443007.61 rows=182295955 width=52) (actual time=183725.696..205882.889 rows=172 loops=1) 
    Group Key: ((fivi.created_ts)::date), r.referer, c.country 
    -> Sort (cost=38893491.99..38984639.97 rows=182295955 width=52) (actual time=183725.655..200899.098 rows=8390217 loops=1) 
     Sort Key: ((fivi.created_ts)::date), r.referer, c.country 
     Sort Method: external merge Disk: 420192kB 
     -> Hash Left Join (cost=16340128.88..24989809.75 rows=182295955 width=52) (actual time=23399.900..104337.332 rows=8390217 loops=1) 
       Hash Cond: (v.country_id = c.country_id) 
       -> Hash Left Join (cost=16340125.36..24800637.72 rows=182295955 width=49) (actual time=23399.782..102534.655 rows=8390217 loops=1) 
        Hash Cond: (v.referer_id = r.referer_id) 
        -> Merge Join (cost=16340033.52..24051874.62 rows=182295955 width=29) (actual time=23397.410..99955.000 rows=8390217 loops=1) 
          Merge Cond: (fivi.view_id = v.view_id) 
          -> Group (cost=16340033.41..16716038.36 rows=182295955 width=16) (actual time=23397.298..30454.444 rows=8390217 loops=1) 
           Group Key: fivi.view_id, ((fivi.created_ts)::date) 
           -> Sort (cost=16340033.41..16434985.73 rows=189904653 width=16) (actual time=23397.294..28165.729 rows=8390217 loops=1) 
             Sort Key: fivi.view_id, ((fivi.created_ts)::date) 
             Sort Method: external merge Disk: 180392kB 
             -> Nested Loop (cost=6530.43..8799350.01 rows=189904653 width=16) (actual time=63.123..15131.956 rows=8390217 loops=1) 
              -> HashAggregate (cost=6530.31..6659.62 rows=43104 width=8) (actual time=62.983..90.331 rows=43887 loops=1) 
                Group Key: file_owner.file_id 
                -> Bitmap Heap Scan on file_owner (cost=342.90..6508.76 rows=43104 width=8) (actual time=5.407..50.779 rows=43887 loops=1) 
                 Recheck Cond: (owner_id = 75) 
                 Heap Blocks: exact=5904 
                 -> Bitmap Index Scan on owner_id_index (cost=0.00..340.74 rows=43104 width=0) (actual time=4.327..4.327 rows=45576 loops=1) 
                   Index Cond: (owner_id = 75) 
              -> Index Scan using file_id_view_id_indexing on file_id_view_id fivi (cost=0.11..188.56 rows=4406 width=24) (actual time=0.122..0.306 rows=191 loops=43887) 
                Index Cond: (file_id = file_owner.file_id) 
                Filter: ((created_ts >= '2015-11-01 00:00:00'::timestamp without time zone) AND (created_ts <= '2015-12-01 00:00:00'::timestamp without time zone)) 
                Rows Removed by Filter: 184 
          -> Index Scan using "Views_pkey" on views v (cost=0.11..5981433.17 rows=338958763 width=25) (actual time=0.088..46804.757 rows=213018702 loops=1) 
        -> Hash (cost=68.77..68.77 rows=6591 width=28) (actual time=2.344..2.344 rows=6495 loops=1) 
          Buckets: 1024 Batches: 1 Memory Usage: 410kB 
          -> Seq Scan on referer r (cost=0.00..68.77 rows=6591 width=28) (actual time=0.006..1.156 rows=6495 loops=1) 
       -> Hash (cost=2.70..2.70 rows=233 width=7) (actual time=0.078..0.078 rows=233 loops=1) 
        Buckets: 1024 Batches: 1 Memory Usage: 10kB 
        -> Seq Scan on country c (cost=0.00..2.70 rows=233 width=7) (actual time=0.005..0.042 rows=233 loops=1) 
Planning time: 1.015 ms 
Execution time: 206034.660 ms 
(37 rows) 

total_views | invalid_views | valid_views | values | day  |  referer  | country 
------------+---------------+-------------+--------+------------+-----------------+--------- 

当与EXPLAIN ANALYZE运行这样的查询产生以下

关于explain.depesz.com的计划:http://explain.depesz.com/s/OiN

206s运行时间。

需要注意以下几点,

PostgreSQL的版本9.4

我已经调整了配置如下:

  1. 的shared_buffers = 30GB
  2. work_mem = 32MB
  3. random_page_cost = 2.0
  4. cpu_tuple_cost = 0.0030
  5. cpu_index_tuple_cost = 0.0010
  6. cpu_operator_cost = 0。0005
  7. effective_cache_size = 52GB

以下指标目前存在:

  1. CREATE INDEX country_index ON国家使用B树(国家);
  2. CREATE INDEX created_ts_index ON file_id_view_id USING btree(created_ts);
  3. CREATE INDEX file_id_created_ts_index ON file_id_view_id USING btree(created_ts,file_id);
  4. CREATE INDEX file_id_view_id_indexing ON file_id_view_id USING btree(file_id);
  5. CREATE INDEX owner_id_file_id_index ON file_owner USING btree(file_id,owner_id);
  6. CREATE INDEX owner_id_index ON file_owner USING btree(owner_id);
  7. CREATE INDEX referer_index ON referer USING btree(referer);

前一次查询使用所有者ID将其拾取保守,某些查询可能导致file_id_view_id表的1/3接合与视图

更改数据结构是最后度假村。在这个阶段,这种变化必须是由于严重的担忧。

如果需要,db可以被认为是只读的,正在写入的数据每小时完成一次,并且在每次写入后给Postgres大量的呼吸空间。在当前时刻600K每小时写入 db在1100s内返回(这是由于其他原因以及插入成本)。如果增加读取速度,则有足够的空间添加附加索引,读取速度是优先考虑的。

的硬件规格是:

CPU:http://ark.intel.com/products/83356/Intel-Xeon-Processor-E5-2630-v3-20M-Cache-2_40-GHz

RAM:128GB

储存:1.5TB PCIE SSD

如何优化无论是我的数据库或查询这样我可以在合理的时间范围内检索出我需要的数据。

我可以做些什么来优化我目前的设计?

我相信Postgres及其运行的硬件具有比目前好得多的性能。

UPDATE

我曾尝试:

  1. 分析表,并不会影响性能。
  2. 增加work_mem,导致速度增加到116s。
  3. 依靠Postgres的查询规划器避免子选择,这会对性能产生负面影响。
  4. 单独的db查询在手之前,这似乎没有正面/负面影响。

有没有人有任何经验重组表格这个大?这可行吗?这需要几天,几小时(估计当然)?

我正在考虑对数据库进行反规范化处理,因为它只会在此方法中引用。我唯一担心的是 - 如果从索引owner_id的表中调用100M行,速度足够快还是仍然会面临相同的性能问题?会讨厌以某种方式走回头路。

我正在研究的另一个解决方案是@ ivan.panasuik建议,将全天数据分组到另一个表中,因为一旦过去了一天,信息就是不变的,不需要更改或更新。然而,我不确定如何顺利实现这一点 - 我应该在插入处于暂挂状态时通过数据查询运行并尽可能快地捕捉日期?从那时起有触发器设置?

+2

估计并不是那么准确。你分析过涉及的表格吗?您还有两个相当大的排序在磁盘上完成。您可以尝试大幅增加work_mem _对于该查询_例如'set work_mem ='512MB''或甚至'set work_mem ='1GB'' –

+0

我在印象之下Postgres会自动分析这些表格,我是否应该手动执行它?当你说_that query_你的意思是有一个特定的方式来设置单个查询的work_mem? –

+0

它应该自动做到这一点,但有时(例如在初始加载后)它不能足够快地踢入。我在查询中运行_before_时显示的语句将改变当前会话的'work_mem':http://www.postgresql.org/docs/current/static/sql-set.html –

回答

2

数据库的速度通常不是你的硬件,而是你如何使用引擎本身的智能和功能。有很多数据的时候特别 -

  1. 尽量避免子查询。这些通常无法通过查询规划器进行优化。在大多数情况下,如果需要,您应该能够将简单的子查询转换为JOIN,甚至可以在手之前单独进行数据库查找。

  2. 对你的表进行分区 - PostgreSQL本身并不这样做,但如果你经常只访问最近的数据,你可以通过移动归档数据来移除很多工作。

  3. 考虑一个数据仓库策略 - 当你处理这些数据时,你应该考虑以一种非规范化的方式存储数据的副本,因为讨厌的JOIN已经被处理过了,所以非常快速地检索数据。我们使用Redshift(PostgeSQL的衍生产品)做到这一点,以便在运行报表时不需要执行任何JOIN。

+0

我喜欢你的第三个建议,我正在处理的一些东西......保持临时缓存完全展开的数据,并与有效存储的数据并行,直到合理的窗口通过。谢谢! –

2
  1. 删除(计数(英尺*) - SUM(ft.valid))AS invalid_views,既然你已经有了这个价值,你可以在以后计算的话,在显示效果
  2. 添加指数file_owner.file_id并检查在查询中使用的每个字段都有索引(您在条件中使用的字段:where,group等)
  3. 我没有更多地分析查询,但似乎应该分割查询几个更小(更快)的查询并使用临时表或存储过程进行连接。
  4. 假设昨天的结果是不会改变的......你可以在day = today()条件下运行查询并避免按天分组。所有的日子结果,你可以保存在一个单独的表。我发现大部分时间都是分组的。

它很难预测优化没有尝试和尝试....所以一个一个地尝试。还有祝你好运。