2012-12-04 122 views
7

我在PostgreSQL 9.1中有兩個表 - flight_2012_09_12包含約500,000行,position_2012_09_12包含約550萬行。我正在運行一個簡單的連接查詢,並且需要很長時間才能完成,儘管表格並不小,但我確信在執行過程中會獲得一些重大收益。優化postgresql查詢

查詢是:

SELECT f.departure, f.arrival, 
     p.callsign, p.flightkey, p.time, p.lat, p.lon, p.altitude_ft, p.speed 
FROM position_2012_09_12 AS p 
JOIN flight_2012_09_12 AS f 
    ON p.flightkey = f.flightkey 
WHERE p.lon < 0 
     AND p.time BETWEEN '2012-9-12 0:0:0' AND '2012-9-12 23:0:0' 

的講解分析的輸出是:

Hash Join (cost=239891.03..470396.82 rows=4790498 width=51) (actual time=29203.830..45777.193 rows=4403717 loops=1) 
Hash Cond: (f.flightkey = p.flightkey) 
-> Seq Scan on flight_2012_09_12 f (cost=0.00..1934.31 rows=70631 width=12) (actual time=0.014..220.494 rows=70631 loops=1) 
-> Hash (cost=158415.97..158415.97 rows=3916885 width=43) (actual time=29201.012..29201.012 rows=3950815 loops=1) 
    Buckets: 2048 Batches: 512 (originally 256) Memory Usage: 1025kB 
    -> Seq Scan on position_2012_09_12 p (cost=0.00..158415.97 rows=3916885 width=43) (actual time=0.006..14630.058 rows=3950815 loops=1) 
      Filter: ((lon < 0::double precision) AND ("time" >= '2012-09-12 00:00:00'::timestamp without time zone) AND ("time" <= '2012-09-12 23:00:00'::timestamp without time zone)) 
Total runtime: 58522.767 ms 

我認爲問題出在位置表的順序掃描,但我想不出爲什麼在那。與索引表結構如下:

   Table "public.flight_2012_09_12" 
    Column  |   Type    | Modifiers 
--------------------+-----------------------------+----------- 
callsign   | character varying(8)  | 
flightkey   | integer      | 
source    | character varying(16)  | 
departure   | character varying(4)  | 
arrival   | character varying(4)  | 
original_etd  | timestamp without time zone | 
original_eta  | timestamp without time zone | 
enroute   | boolean      | 
etd    | timestamp without time zone | 
eta    | timestamp without time zone | 
equipment   | character varying(6)  | 
diverted   | timestamp without time zone | 
time    | timestamp without time zone | 
lat    | double precision   | 
lon    | double precision   | 
altitude   | character varying(7)  | 
altitude_ft  | integer      | 
speed    | character varying(4)  | 
asdi_acid   | character varying(4)  | 
enroute_eta  | timestamp without time zone | 
enroute_eta_source | character varying(1)  | 
Indexes: 
"flight_2012_09_12_flightkey_idx" btree (flightkey) 
"idx_2012_09_12_altitude_ft" btree (altitude_ft) 
"idx_2012_09_12_arrival" btree (arrival) 
"idx_2012_09_12_callsign" btree (callsign) 
"idx_2012_09_12_departure" btree (departure) 
"idx_2012_09_12_diverted" btree (diverted) 
"idx_2012_09_12_enroute_eta" btree (enroute_eta) 
"idx_2012_09_12_equipment" btree (equipment) 
"idx_2012_09_12_etd" btree (etd) 
"idx_2012_09_12_lat" btree (lat) 
"idx_2012_09_12_lon" btree (lon) 
"idx_2012_09_12_original_eta" btree (original_eta) 
"idx_2012_09_12_original_etd" btree (original_etd) 
"idx_2012_09_12_speed" btree (speed) 
"idx_2012_09_12_time" btree ("time") 

      Table "public.position_2012_09_12" 
Column |   Type    | Modifiers 
-------------+-----------------------------+----------- 
callsign | character varying(8)  | 
flightkey | integer      | 
time  | timestamp without time zone | 
lat   | double precision   | 
lon   | double precision   | 
altitude | character varying(7)  | 
altitude_ft | integer      | 
course  | integer      | 
speed  | character varying(4)  | 
trackerkey | integer      | 
the_geom | geometry     | 
Indexes: 
"index_2012_09_12_altitude_ft" btree (altitude_ft) 
"index_2012_09_12_callsign" btree (callsign) 
"index_2012_09_12_course" btree (course) 
"index_2012_09_12_flightkey" btree (flightkey) 
"index_2012_09_12_speed" btree (speed) 
"index_2012_09_12_time" btree ("time") 
"position_2012_09_12_flightkey_idx" btree (flightkey) 
"test_index" btree (lon) 
"test_index_lat" btree (lat) 

我想不出任何其他方式重寫查詢,所以我在這一點難倒。如果當前的設置是如此的好,但在我看來,它應該比現在快得多。任何幫助將非常感激。

+0

你能提供public.position_2012_09_12表lon和時間列的統計信息?也許有些(時間)lon <0索引會有所幫助,但在位置表中有3950815行與此條件匹配。這個表中有更多的數據嗎? – sufleR

+0

有5563070行該表(編輯我的帖子反映,而不是我原來規定的350萬美元) – TheOx

+2

您正在使用哪個的版本的PostgreSQL? – plang

回答

2

你得到順序掃描的原因是Postgres認爲它會比使用索引讀取更少的磁盤頁面。這可能是對的。考慮一下,如果您使用非覆蓋索引,則需要讀取所有匹配的索引頁面。它本質上輸出一個行標識符列表。數據庫引擎然後需要讀取每個匹配的數據頁面。

你的位置表每行使用71個字節,加上任何一個geom類型需要的(我將假定16字節用於說明),製作87個字節。 Postgres頁面是8192字節。所以你每頁有大約90行。

您的查詢與5563070行中的3950815個匹配,大約佔總數的70%。假設數據是隨機分佈的,就您的過濾器而言,找到沒有匹配行的數據頁面幾乎有30%^ 90的機會。這本質上沒有什麼。所以不管你的索引有多好,你仍然需要閱讀所有的數據頁面。如果你將不得不閱讀所有的頁面,表掃描通常是一個好方法。

的一個走出這裏,是我說的非覆蓋索引。如果您準備創建可以自己回答查詢的索引,則可以避免查找數據頁面,以便重新回到遊戲中。我建議以下是值得看的:

flight_2012_09_12 (flightkey, departure, arrival) 
position_2012_09_12 (filghtkey, time, lon, ...) 
position_2012_09_12 (lon, time, flightkey, ...) 
position_2012_09_12 (time, long, flightkey, ...) 

這裏的點代表您選擇的列的其餘部分。你只需要一個位置指標,但很難說哪一個最好。第一種方法可以允許對預分類數據進行合併連接,讀取整個第二索引以進行過濾。第二個和第三個將允許數據被預先過濾,但需要散列連接。給出多少成本似乎在散列連接中,合併連接可能是一個很好的選擇。

當你的查詢需要每行87個字節的52,且索引的開銷,你可能不索引服用多了,如果有的話,不佔空間,然後表本身結束。

另一種方法是攻擊它的「隨機分佈」的一面,通過查看集羣。

+1

它在我看來並不值得在飛行表上增加一個覆蓋指數,因爲全面掃描似乎只需要220ms? –

+0

@DavidAldridge公平點,雖然在兩個表上都有一個以飛行鍵開頭的覆蓋索引可以允許一個合併連接,我希望這個連接比預先排序的數據上的散列連接要快。 – Laurence

+0

@DavidAldridge用戶在PostgreSQL 9.1上,它沒有索引掃描(像覆蓋索引),所以問題仍然沒有解決。 –

3

行數估計是非常合理的,所以我懷疑這是一個統計問題。

我想嘗試:

  • position_2012_09_12("time") WHERE (lon < 0)創建於position_2012_09_12(lon,"time")索引或可能部分索引,如果您經常搜索lon < 0

  • 設置random_page_cost更低,也許1.1。看看是否(a)這改變了計劃,以及(b)如果新計劃實際上更快。爲了測試目的,看看是否避免seqscan會更快,你可以SET enable_seqscan = off;如果是,請更改成本參數。

  • 對此查詢增加work_mem。運行它之前或之前。

  • 運行最新的PostgreSQL,如果你還沒有。總是在問題中指定PostgreSQL版本。 (編輯後更新):你在9.1;沒關係。 9.2中最大的性能改進是僅索引掃描,並且您似乎很可能不會從僅查詢索引的掃描中獲益。

如果您可以擺脫列來縮小行數,那麼您還可以在某種程度上提高性能。它不會造成很大的差異,但它會做一些。