2015-12-14 30 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()條件下運行查詢並避免按天分組。所有的日子結果,你可以保存在一個單獨的表。我發現大部分時間都是分組的。

它很難預測優化沒有嘗試和嘗試....所以一個一個地嘗試。還有祝你好運。