2012-12-04 13 views
7

mam 2 PostgreSQL tabele 9.1 - flight_2012_09_12 zawierający około 500,000 rzędów position_2012_09_12 zawierający około 5,5 miliona wierszy. Używam prostego zapytania o dołączenie, a jego wypełnienie zajmuje dużo czasu i pomimo faktu, że tabele nie są małe, jestem przekonany, że w egzekucji można uzyskać znaczne korzyści.Optymalizacja zapytanie postgresql

Kwerenda jest:

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' 

Wyjście wyjaśnić analizować to:

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 

Myślę, że problem leży po stronie sekwencyjnego skanowania na tablicy pozycji, ale nie mogę zrozumieć, dlaczego jest tutaj. Struktury tabeli z indeksami są poniżej:

   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) 

nie mogę wymyślić inny sposób przepisać kwerendy i tak jestem zakłopotany w tym momencie. Jeśli obecna konfiguracja jest tak dobra, jak to możliwe, ale wydaje mi się, że powinna być znacznie szybsza niż obecnie. Każda pomoc byłaby bardzo cenna.

+0

Czy możesz podać statystyki dotyczące kolumn tabeli public time i kolumny public.position_2012_09_12? Może trochę (czas), gdzie lon <0 index pomoże, ale jest 3950815 wierszy w tabeli pozycji, które pasują do tych warunków. Czy w tej tabeli jest więcej danych? – sufleR

+0

W tej tabeli znajduje się 5563070 wierszy (zredagowano mój wpis, aby odzwierciedlić to zamiast 3,5 miliona, które pierwotnie napisałem). – TheOx

+2

Którą wersję PostgreSQL używasz? – plang

Odpowiedz

2

Powodem są coraz sekwencyjne skanowanie jest to, że Postgres wierzy, że będzie czytać mniej stron dyskowych że sposób niż za pomocą indeksów. Prawdopodobnie ma rację. Zastanów się, jeśli korzystasz z indeksu niekryjącego, musisz przeczytać wszystkie pasujące strony indeksu. zasadniczo wyświetla listę identyfikatorów wierszy. Silnik bazy danych musi następnie odczytać każdą z pasujących stron danych.

Twoja tabela pozycji używa 71 bajtów na wiersz, a także cokolwiek innego typu geom (przyjmuję 16 bajtów dla ilustracji), co daje 87 bajtów. Strona Postgres to 8192 bajty. Więc masz około 90 wierszy na stronach.

Twoje zapytanie pasuje do 3950815 z 556 3070 wierszy lub około 70% całości.Zakładając, że dane są losowo rozmieszczone, w odniesieniu do twoich filtrów, istnieje szansa, że ​​znajdziesz 30%^90 stronę z danymi bez pasującego wiersza. To w zasadzie nic. Niezależnie od tego, jak dobre są twoje indeksy, nadal będziesz musiał przeczytać wszystkie strony z danymi. Jeśli mimo wszystko będziesz musiał przeczytać wszystkie strony, skanowanie tabeli jest zazwyczaj dobrym pomysłem.

Jedną z nich jest to, że powiedziałem, że wskaźnik niekryje. Jeśli jesteś przygotowany do tworzenia indeksów, które same mogą odpowiadać na zapytania, możesz w ogóle uniknąć przeglądania stron z danymi, więc powróciłeś do gry. Sugeruję następujące wartości:

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, ...) 

Kropki reprezentują pozostałe kolumny, które wybierasz. Będziesz potrzebował tylko jednego z indeksów na stanowisku, ale trudno powiedzieć, który z nich okaże się najlepszy. Pierwsze podejście może pozwolić na połączenie scalania na sortowanych danych, z kosztem odczytu całego drugiego indeksu do filtrowania. Drugi i trzeci pozwolą na prefiltrację danych, ale wymagają sprzężenia hash. Podaj, ile kosztu wydaje się być w sprzężeniu hash, połączenie łączenia może być dobrym rozwiązaniem.

Ponieważ zapytanie wymaga 52 z 87 bajtów na wiersz, a indeksy mają koszty ogólne, możesz nie skończyć z indeksem zajmującym dużo, jeśli w ogóle, mniej miejsca niż sama tabela.

Innym podejściem jest atakowanie strony "losowo rozmieszczonej", patrząc na klaster.

+1

Nie wyglądało na to, że warto dodać indeks zakrywający na stole lotu, ponieważ pełne skanowanie zajmuje tylko 220ms? –

+0

@DavidAldridge Punkt prawidłowy, chociaż posiadanie indeksu pokrywającego, zaczynającego się od klucza lotu na obu stołach, może pozwolić na łączenie się, które prawdopodobnie będzie szybsze niż sprzężenie hashowe na wstępnie posortowanych danych. – Laurence

+0

@DavidAldridge Użytkownik jest na PostgreSQL 9.1, który nie ma skanów tylko do indeksu (jak indeksy obejmujące), więc i tak problem jest dyskusyjny. –

3

Szacowane liczby wierszy są dość rozsądne, więc wątpię, aby to był problem ze statystykami.

będę próbować:

  • Tworzenie indeksu na position_2012_09_12(lon,"time") lub ewentualnie częściowe indeksu na position_2012_09_12("time") WHERE (lon < 0) jeśli rutynowo szukać lon < 0.

  • Ustawianie random_page_cost niższy, może 1,1. Sprawdź, czy (a) to zmienia plan i (b) czy nowy plan jest rzeczywiście szybszy. Aby sprawdzić, czy uniknięcie seqscanu może być szybsze, możesz: SET enable_seqscan = off; jeśli tak, zmień parametry kosztów.

  • Wzrost work_mem dla tego zapytania. SET work_mem = 10M lub coś przed uruchomieniem.

  • Uruchamianie najnowszego PostgreSQL, jeśli jeszcze nie jesteś. Zawsze podawaj swoją wersję PostgreSQL w pytaniach. (Aktualizacja po edycji): jesteś na 9.1; w porządku. Największą poprawą wydajności w wersji 9.2 były skany zindeksowane i nie wydaje się prawdopodobne, aby z tego zapytania uzyskać masową korzyść ze skanowania tylko do indeksu.

Możesz także nieco poprawić wydajność, jeśli możesz pozbyć się kolumn, aby zawęzić wiersze. Nie zrobi to wielkiej różnicy, ale to da trochę.

Powiązane problemy