Skanowanie sekwencyjne PostgreSQL zamiast skanowania indeksu Dlaczego?

12

Cześć Wszystko Mam problem z zapytaniem do bazy danych PostgreSQL i zastanawiam się, czy ktoś może pomóc. W niektórych scenariuszach moje zapytanie wydaje się ignorować utworzony przeze mnie indeks, który służy do łączenia dwóch tabel datai data_area. Kiedy tak się dzieje, wykorzystuje skanowanie sekwencyjne i powoduje znacznie wolniejsze zapytanie.

Skanowanie sekwencyjne (~ 5 minut)

Unique  (cost=15368261.82..15369053.96 rows=200 width=1942) (actual time=301266.832..301346.936 rows=153812 loops=1)
   CTE data
     ->  Bitmap Heap Scan on data  (cost=6086.77..610089.54 rows=321976 width=297) (actual time=26.286..197.625 rows=335130 loops=1)
           Recheck Cond: (datasetid = 1)
           Filter: ((readingdatetime >= '1920-01-01 00:00:00'::timestamp without time zone) AND (readingdatetime <= '2013-03-11 00:00:00'::timestamp without time zone) AND (depth >= 0::double precision) AND (depth <= 99999::double precision))
           ->  Bitmap Index Scan on data_datasetid_index  (cost=0.00..6006.27 rows=324789 width=0) (actual time=25.462..25.462 rows=335130 loops=1)
                 Index Cond: (datasetid = 1)
   ->  Sort  (cost=15368261.82..15368657.89 rows=158427 width=1942) (actual time=301266.829..301287.110 rows=155194 loops=1)
         Sort Key: data.id
         Sort Method: quicksort  Memory: 81999kB
         ->  Hash Left Join  (cost=15174943.29..15354578.91 rows=158427 width=1942) (actual time=300068.588..301052.832 rows=155194 loops=1)
               Hash Cond: (data_area.area_id = area.id)
               ->  Hash Join  (cost=15174792.93..15351854.12 rows=158427 width=684) (actual time=300066.288..300971.644 rows=155194 loops=1)
                     Hash Cond: (data.id = data_area.data_id)
                     ->  CTE Scan on data  (cost=0.00..6439.52 rows=321976 width=676) (actual time=26.290..313.842 rows=335130 loops=1)
                     ->  Hash  (cost=14857017.62..14857017.62 rows=25422025 width=8) (actual time=300028.260..300028.260 rows=26709939 loops=1)
                           Buckets: 4194304  Batches: 1  Memory Usage: 1043357kB
                           ->  Seq Scan on data_area  (cost=0.00..14857017.62 rows=25422025 width=8) (actual time=182921.056..291687.996 rows=26709939 loops=1)
                                 Filter: (area_id = ANY ('{28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11}'::integer[]))
               ->  Hash  (cost=108.49..108.49 rows=3349 width=1258) (actual time=2.256..2.256 rows=3349 loops=1)
                     Buckets: 1024  Batches: 1  Memory Usage: 584kB
                     ->  Seq Scan on area  (cost=0.00..108.49 rows=3349 width=1258) (actual time=0.007..0.666 rows=3349 loops=1)
 Total runtime: 301493.379 ms

Skanowanie indeksu (~ 3 sekundy) ( na replace.depesz.com )

Unique  (cost=17352256.47..17353067.50 rows=200 width=1942) (actual time=3603.303..3681.619 rows=153812 loops=1)
   CTE data
     ->  Bitmap Heap Scan on data  (cost=6284.60..619979.56 rows=332340 width=297) (actual time=26.201..262.314 rows=335130 loops=1)
           Recheck Cond: (datasetid = 1)
           Filter: ((readingdatetime >= '1920-01-01 00:00:00'::timestamp without time zone) AND (readingdatetime <= '2013-03-11 00:00:00'::timestamp without time zone) AND (depth >= 0::double precision) AND (depth <= 99999::double precision))
           ->  Bitmap Index Scan on data_datasetid_index  (cost=0.00..6201.51 rows=335354 width=0) (actual time=25.381..25.381 rows=335130 loops=1)
                 Index Cond: (datasetid = 1)
   ->  Sort  (cost=17352256.47..17352661.98 rows=162206 width=1942) (actual time=3603.302..3623.113 rows=155194 loops=1)
         Sort Key: data.id
         Sort Method: quicksort  Memory: 81999kB
         ->  Hash Left Join  (cost=1296.08..17338219.59 rows=162206 width=1942) (actual time=29.980..3375.921 rows=155194 loops=1)
               Hash Cond: (data_area.area_id = area.id)
               ->  Nested Loop  (cost=0.00..17334287.66 rows=162206 width=684) (actual time=26.903..3268.674 rows=155194 loops=1)
                     ->  CTE Scan on data  (cost=0.00..6646.80 rows=332340 width=676) (actual time=26.205..421.858 rows=335130 loops=1)
                     ->  Index Scan using data_area_pkey on data_area  (cost=0.00..52.13 rows=1 width=8) (actual time=0.006..0.008 rows=0 loops=335130)
                           Index Cond: (data_id = data.id)
                           Filter: (area_id = ANY ('{28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11}'::integer[]))
               ->  Hash  (cost=1254.22..1254.22 rows=3349 width=1258) (actual time=3.057..3.057 rows=3349 loops=1)
                     Buckets: 1024  Batches: 1  Memory Usage: 584kB
                     ->  Index Scan using area_primary_key on area  (cost=0.00..1254.22 rows=3349 width=1258) (actual time=0.012..1.429 rows=3349 loops=1)
 Total runtime: 3706.630 ms

Struktura tabeli

To jest struktura tabeli dla data_areatabeli. W razie potrzeby mogę podać inne tabele.

CREATE TABLE data_area
(
  data_id integer NOT NULL,
  area_id integer NOT NULL,
  CONSTRAINT data_area_pkey PRIMARY KEY (data_id , area_id ),
  CONSTRAINT data_area_area_id_fk FOREIGN KEY (area_id)
      REFERENCES area (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT data_area_data_id_fk FOREIGN KEY (data_id)
      REFERENCES data (id) MATCH SIMPLE
      ON UPDATE CASCADE ON DELETE CASCADE
);

PYTANIE

WITH data AS (
    SELECT * 
    FROM data 
    WHERE 
        datasetid IN (1) 
        AND (readingdatetime BETWEEN '1920-01-01' AND '2013-03-11') 
        AND depth BETWEEN 0 AND 99999
)
SELECT * 
FROM ( 
    SELECT DISTINCT ON (data.id) data.id, * 
    FROM 
        data, 
        data_area 
        LEFT JOIN area ON area_id = area.id 
    WHERE 
        data_id = data.id 
        AND area_id IN (28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11) 
) as s;

Zwraca 153812wiersze. Czy set enable_seqscan= false;wyłączyć sekwencyjne skanowanie i uzyskać wynik indeksu.

Próbowałem zrobić ANALYSEna bazie danych i zwiększyć statystyki zebrane na kolumnach użytych w zapytaniu, ale nic nie pomaga.

Czy ktoś może się tym zająć i zasugerować coś innego lub zasugerować coś innego, co powinienem spróbować?

Mark Davidson
źródło
Pomogłoby mi to, gdybyś uwzględnił zapytania, które wygenerowały każdy z tych planów wykonania.
Mike Sherrill „Cat Recall”
Różnica 2 rzędów wielkości w szacowanej liczbie rzędów i rzeczywistej liczbie rzędów? Czy dobrze to czytam?
Mike Sherrill „Cat Recall”
@Catcall Dodałem zapytanie (trochę fundamentalne, aby móc dowiedzieć się, co się dzieje). Kiedy odwołujesz się do szacunkowych wierszy, czy 200, a następnie faktycznie zwraca 153812?
Mark Davidson,
2
Tak, 200 na 150k na pierwszy rzut oka wydaje się dziwne. Czy istnieje ważny powód, aby połączyć lewe złączenie z produktem kartezjańskim ( FROM data, data_area)? Na pierwszy rzut oka używanie DISTINCT ON bez klauzuli ORDER BY wydaje się złym pomysłem.
Mike Sherrill „Cat Recall”
wyjaśnić.depesz.com/s/Uzin może mieć charakter informacyjny.
Craig Ringer

Odpowiedzi:

8

Zwróć uwagę na ten wiersz:

->  Index Scan using data_area_pkey on data_area  (cost=0.00..52.13 rows=1 width=8) 
    (actual time=0.006..0.008 rows=0 loops=335130)

Jeśli obliczysz całkowity koszt, biorąc pod uwagę pętle, tak jest 52.3 * 335130 = 17527299. Jest to więcej niż 14857017.62 dla seq_scanalternatywy. Dlatego nie używa indeksu.

Optymalizator przecenia więc koszt skanowania indeksu. Domyślam się, że twoje dane są posortowane według indeksu (albo ze względu na indeks klastrowy, albo z powodu sposobu jego załadowania) i / lub masz dużo pamięci podręcznej i / lub ładny szybki dysk. Dlatego mało jest przypadkowych operacji we / wy.

Należy również sprawdzić correlationin pg_stats, który jest używany przez optymalizator do oceny klastrów przy obliczaniu kosztów indeksu, a wreszcie spróbować zmienić random_page_costi cpu_index_tuple_cost, aby dopasować swój system.

jop
źródło
O ile nie jestem czegoś brakuje, myślę @jop oznaczało 52.13, nie 52.3, co skutkowałoby 17470326.9 (jeszcze większe niż seq_scan)
botnet
2

Twoja CTE faktycznie nie robi nic więcej niż „outsourcing” kilku WHEREwarunków, z których większość wygląda na równoważną WHERE TRUE. Ponieważ CTE zwykle znajdują się za płotem optymalizacyjnym (co oznacza, że ​​jest ono zoptymalizowane samodzielnie), mogą bardzo pomóc w przypadku niektórych zapytań. W tym przypadku oczekiwałbym jednak dokładnie odwrotnego efektu.

Chciałbym przepisać zapytanie tak, aby było jak najprostsze:

SELECT d.id, * 
FROM 
    data d 
    JOIN data_area da ON da.data_id = d.id
    LEFT JOIN area a ON da.area_id = a.id 
WHERE 
    d.datasetid IN (1) 
    AND da.area_id IN (28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11) 
    AND (readingdatetime BETWEEN '1920-01-01' AND '2013-03-11') -- this and the next condition don't do anything, I think
    AND depth BETWEEN 0 AND 99999
;

a następnie sprawdź, czy indeks jest używany, czy nie. Nadal bardzo możliwe jest, że nie potrzebujesz wszystkich kolumn wyjściowych (przynajmniej dwie kolumny tabeli połączeń są zbędne).

Zgłoś się i powiedz nam, której wersji PostgreSQL używasz.

dezso
źródło
Dziękuję za sugestię, przepraszam za opóźnioną odpowiedź na Twój post. Pracowałem nad innymi projektami. Twoja sugestia rzeczywiście oznacza, że ​​zapytanie wydaje się teraz niezawodnie używać indeksu dla wszystkich zapytań, ale nadal nie uzyskuję wydajności, której bym się spodziewał. Zrobiłem analizować na kwerendzie, że ma dużo więcej danych explain.depesz.com/s/1yu bierze jak 4 minuty z 95% czasu są wydawane na skanie INDEX.
Mark Davidson,
Zapomniałem wspomnieć, że używam wersji 9.1.4
Mark Davidson,
Zasadniczo skanowanie indeksu jest dość szybkie, problem polega na tym, że powtarza się kilka milionów razy. Co otrzymasz, jeśli SET enable_nestloop=offuruchomisz kwerendę?
dezso,
-1

Dla obserwujących miałem podobny problem

select * from table where bigint_column between x and y and mod(bigint_column, 10000) == z

Problem polegał na tym, że moja bigint_column „między xiy” miała indeks, ale moje zapytanie było w zasadzie „wszystkimi wierszami” w tej tabeli, więc nie używało indeksu [ponieważ i tak musiał skanować całą tabelę], ale robiłem sekwencyjne skanowanie sekwencyjne. Rozwiązaniem było dla mnie utworzenie nowego indeksu po stronie „mod” równania, aby mógł on użyć tego wyrażenia .

rogerdpack
źródło
downvoters mogą dodawać komentarze, dlaczego :)
rogerdpack