2013-05-29 166 views
0

我在PostgreSQL 8.2的分叉版本上已經適應了MPP。我試圖從兩個較大的表格中計算出一系列最佳下界的最大下界。這裏是前述方式表的例子:優化涉及一組時間最大下界的查詢PostgreSQL

Table A 
|source_ip (inet type) |s_time (date type) | 
------------------------------------------------ 
|10.50.43.200   | 2013-02-21 01:47:08 | 
|10.50.43.200   | 2013-02-21 01:47:38 | 
|10.50.43.200   | 2013-02-21 01:47:41 | 
|10.50.43.200   | 2013-02-25 17:05:00 | 
|10.50.43.200   | 2013-02-25 17:05:03 | 
|10.50.43.200   | 2013-02-25 17:05:04 | 
|10.50.43.200   | 2013-02-25 17:05:34 | 
|10.50.43.200   | 2013-02-25 17:10:01 | 
|10.50.43.200   | 2013-02-25 17:12:52 | 

Table B 
|source_ip (inet type) |mac (macaddr type) |l_time (date type) | 
---------------------------------------------------------------------- 
|10.50.43.200   | 00:24:d7:99:e9:0c | 2013-02-20 22:33:47 | 
|10.50.43.200   | 00:24:d7:99:e9:0c | 2013-02-20 23:07:32 | 
|10.50.43.200   | 00:24:d7:99:e9:0c | 2013-02-20 23:13:04 | 
|10.50.43.200   | 00:24:d7:99:e9:0c | 2013-02-21 00:02:56 | 
|10.50.43.200   | 00:24:d7:99:68:14 | 2013-02-25 17:04:56 | 
|10.50.43.200   | 00:24:d7:99:68:14 | 2013-02-25 17:04:59 | 
|10.50.43.200   | 00:24:d7:99:68:14 | 2013-02-25 17:26:15 | 

對於表A我想加入一個附加列,那就是「最大下界」爲表B每個時間戳的每一行。也就是說,我想要一個包含表B中所有值中的最大時間的列,並且也小於或等於表A中的相應時間。我期待的輸出將類似於以下內容:

OUTPUT 
------------------------------------------------------------ 
|10.50.43.200 |2013-02-21 01:47:38 |2013-02-21 00:02:56 | 
|10.50.43.200 |2013-02-21 01:47:41 |2013-02-21 00:02:56 | 
|10.50.43.200 |2013-02-25 17:05:00 |2013-02-25 17:04:59 | 
|10.50.43.200 |2013-02-25 17:05:03 |2013-02-25 17:04:59 | 
|10.50.43.200 |2013-02-25 17:05:04 |2013-02-25 17:04:59 | 
|10.50.43.200 |2013-02-25 17:05:34 |2013-02-25 17:04:59 | 

下面的查詢是什麼,我想出了,但我不知道,如果使用max()聚合函數是實現這一目標的最佳方式。

所以,我的問題是 - 我們可以在不使用max()的情況下重寫下面的查詢,以便在大型數據集(在100多萬個範圍內)更快?

SELECT a.source_ip, 
a.s_Time, max(b.l_Time) AS max_time 
    FROM table_a AS a 
    INNER JOIN 
    table_b AS b 
    ON (a.source_ip = b.source_ip AND a.s_time > b.l_time) 
    GROUP BY a.source_ip, b.sourcemac, a.s_time 
    ORDER BY a.s_time asc; 

這裏是解釋計劃:

            QUERY PLAN 
------------------------------------------------------------------------------------------------------------------- 
Gather Motion 72:1 (slice1; segments: 72) (cost=1519175930.51..1519305453.91 rows=143915 width=48) 
    -> HashAggregate (cost=1519175930.51..1519305453.91 rows=143915 width=48) 
     Group By: a.source_ip, a.s_time 
     -> Hash Join (cost=991681.79..1169135585.55 rows=648222862 width=23) 
       Hash Cond: a.source_ip = b.source_ip 
       Join Filter: a.s_time > b.l_time 
       -> Append-only Columnar Scan on a (cost=0.00..1083707.12 rows=1439149 width=15) 
       -> Hash (cost=487360.24..487360.24 rows=560358 width=15) 
        -> Seq Scan on b (cost=0.00..487360.24 rows=560358 width=15) 
(9 rows) 

我知道我可以哈希source_ip -s來bigints更快的連接。我也認爲可能值得嘗試索引連接中使用的列,但我不確定最佳優化策略是什麼,並且會喜歡來自StackOverflow社區優秀專家組的任何輸入。我們也嘗試了rank()窗口函數,但是它在我們正在使用的實現上存在問題,並且是我們測試過的這種類型的查詢性能最差的函數,所以理想的策略將有望避免任何窗口函數。

編輯:添加一個索引上source_ipstart_yime爲表A和重寫使用LIMIT 1建議在後查詢:

              QUERY PLAN 
------------------------------------------------------------------------------------------------------------------------------------- 
Gather Motion 72:1 (slice2; segments: 72) (cost=1624120.24..7442384075819.75 rows=145921 width=48) 
    -> HashAggregate (cost=1624120.24..7442384075819.75 rows=145921 width=48) 
     Group By: a.src, a.start_time 
     -> Append-only Columnar Scan on a (cost=0.00..1098806.16 rows=1459206 width=15) 
     SubPlan 1 
      -> Limit (cost=708374.49..708374.51 rows=1 width=15) 
       -> Limit (cost=708374.49..708374.49 rows=1 width=15) 
         -> Sort (cost=708374.49..708376.35 rows=11 width=15) 
          Sort Key (Limit): b.source_ip, b.start_time 
          -> Result (cost=708339.65..708347.10 rows=11 width=15) 
            Filter: $0 = b.source_ip AND $1 > b.start_time 
            -> Materialize for deadlock safety (cost=708339.65..708347.10 rows=11 width=15) 
             -> Broadcast Motion 72:72 (slice1; segments: 72) (cost=0.00..708338.90 rows=11 width=15) 
               -> Seq Scan on b (cost=0.00..708338.90 rows=11 width=15) 
+0

什麼指數的定義? –

+0

請將包含索引和相關部分配置參數的表結構添加到問題中。 (看起來你已經將work_mem設置爲一個非常大的值)BTW:pg-8.2是非常古老的。 – wildplasser

回答

1
SELECT a.src, 
    a.s_Time, 
    (SELECT b.l_time AS max_time 
     FROM table_b AS b WHERE a.source_ip = b.source_ip 
     AND a.s_time > b.l_time 
     ORDER BY b.source_ip DESC, b.l_time DESC /* index on (source_ip, l_time) */ 
     LIMIT 1) 
FROM table_a AS a 
ORDER BY a.start_time; 

我離開了GROUP BY,因爲我沒有看到a.src和我不確定a.s_timea.start_time是不同的列。

無論如何,這個想法是,PG是非常聰明的索引LIMIT 1查詢(至少,最近的版本是;沒有擔保8.2)。非常新的版本可能足夠聰明,可以將MAX轉換爲等效的LIMIT 1查詢,如果需要的話,但我幾乎可以肯定是在8.2之後。

+0

感謝您的建議,我嘗試了你的重寫併發布瞭解釋計劃輸出,它仍然看起來像使用限制而不是max的查詢計劃器中的成本更糟糕。由於某種原因,查詢計劃程序看起來不像是使用索引,應該在兩個表上還是僅在一個表上創建它? – user7980

+1

對於我的想法來說,table_b(l_time)必須有一個索引。這個想法是,而不是一種排序,PG會從索引中獲取最大值。 PG的後續版本能夠在索引字段上採用MAX時推斷出這一點 - 但我相信,一直回到8.2。 –

1

發現的最大不使用MAX()LIMIT的標準方法是使用NOT EXISTS (a record with a larger value),如:

SELECT a.src, a.s_Time 
     , b.l_Time AS max_time 
    FROM table_a AS a 
    JOIN table_b AS b ON b.source_ip = a.source_ip 
        AND b.l_time < a.s_time 
    WHERE NOT EXISTS (
     SELECT * 
     FROM table_b nx 
     WHERE nx.b.source_ip = b.source_ip 
     AND nx.l_time < a.s_time 
     AND nx.l_time > b.l_time 
    );