PostgreSQL Sequential Scan statt Index Scan Warum?

11

Hallo zusammen, ich habe ein Problem mit meiner PostgreSQL-Datenbankabfrage und frage mich, ob jemand helfen kann. In einigen Szenarien scheint meine Abfrage den von mir erstellten Index zu ignorieren, der zum Verbinden der beiden Tabellen dataund verwendet wird data_area. In diesem Fall wird ein sequentieller Scan verwendet, was zu einer viel langsameren Abfrage führt.

Sequentieller Scan (~ 5 Minuten)

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

Index-Scan (~ 3 Sekunden) ( auf EXPLAIN.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

Tabellenstruktur

Dies ist die Tabellenstruktur für die data_areaTabelle. Ich kann die anderen Tabellen bei Bedarf bereitstellen.

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
);

ABFRAGE

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;

Gibt 153812Zeilen zurück. Hat set enable_seqscan= false;den sequentiellen Scan deaktiviert und das Indexergebnis erhalten.

Ich habe versucht, eine ANALYSEDatenbank zu erstellen und die Statistiken für die in der Abfrage verwendeten Spalten zu erhöhen, aber nichts scheint zu helfen.

Könnte jemand dies verbreiten und beleuchten oder etwas anderes vorschlagen, das ich versuchen sollte?

Mark Davidson
quelle
Es würde mir helfen , wenn Sie die Abfragen einbeziehen würden, die jeden dieser Ausführungspläne generiert haben.
Mike Sherrill 'Cat Recall'
Eine Differenz von 2 Größenordnungen in der geschätzten Anzahl von Zeilen und der tatsächlichen Anzahl von Zeilen? Lese ich das richtig
Mike Sherrill 'Cat Recall'
@Catcall Habe die Abfrage hinzugefügt (ein bisschen grundlegend, um herauszufinden, was los ist). Wenn Sie sich auf die geschätzten Zeilen beziehen, ist das die 200 und dann ist es tatsächlich 153812 zurück?
Mark Davidson
2
Ja, 200 vs 150k scheinen auf den ersten Blick seltsam. Gibt es einen zwingenden Grund, eine linke Verknüpfung mit einem kartesischen Produkt zu mischen ( FROM data, data_area)? Auf den ersten Blick scheint die Verwendung von DISTINCT ON ohne ORDER BY-Klausel eine schlechte Idee zu sein.
Mike Sherrill 'Cat Recall'
EXPLAIN.depesz.com/s/Uzin könnte informativ sein.
Craig Ringer

Antworten:

7

Beachten Sie diese Zeile:

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

Wenn Sie die Gesamtkosten unter Berücksichtigung von Schleifen berechnen, ist dies der Fall 52.3 * 335130 = 17527299. Dies ist größer als 14857017.62 für die seq_scanAlternative. Deshalb wird der Index nicht verwendet.

Der Optimierer überschätzt also die Kosten für den Index-Scan. Ich würde vermuten, dass Ihre Daten im Index sortiert sind (entweder aufgrund eines Clustered-Index oder aufgrund der Art und Weise, wie sie geladen wurden) und / oder Sie über ausreichend Cache-Speicher und / oder eine schöne schnelle Festplatte verfügen. Daher gibt es wenig zufällige E / A.

Sie sollten auch die Check correlationin pg_stats, das vom Optimierungsprogramm verwendet wird Clustering zu bewerten , wenn der Index Kosten der Berechnung und schließlich zu versuchen , zu ändern random_page_costund cpu_index_tuple_costIhr System anzupassen.

jop
quelle
Wenn ich nichts vermisse, denke ich, dass @jop nicht gemeint 52.13ist 52.3, was zu 17470326.9 (immer noch größer als der seq_scan) führen würde
BotNet
2

Ihr CTE tut eigentlich nichts anderes, als ein paar WHEREBedingungen auszulagern , von denen die meisten gleichwertig aussehen WHERE TRUE. Da sich CTEs normalerweise hinter einem Optimierungszaun befinden (was bedeutet, dass er selbst optimiert wird), können sie bei bestimmten Abfragen sehr hilfreich sein. In diesem Fall würde ich jedoch den genau entgegengesetzten Effekt erwarten.

Ich würde versuchen, die Abfrage so einfach wie möglich umzuschreiben:

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
;

und prüfen Sie dann, ob der Index verwendet wird oder nicht. Es ist immer noch sehr wahrscheinlich, dass Sie nicht alle Ausgabespalten benötigen (zumindest die beiden Spalten der Junction-Tabelle sind überflüssig).

Bitte melden Sie sich zurück und teilen Sie uns mit, welche PostgreSQL-Version Sie verwenden.

dezso
quelle
Vielen Dank für Ihren Vorschlag. Ich entschuldige mich für die verspätete Antwort auf Ihren Beitrag. Ich habe an anderen Projekten gearbeitet. Ihr Vorschlag bedeutet zwar, dass die Abfrage den Index jetzt zuverlässig für alle Abfragen zu verwenden scheint, aber ich erhalte immer noch nicht die Leistung, die ich damit erwarten würde. Ich habe eine Analyse für eine Abfrage durchgeführt, die viel mehr Daten enthält. EXPLAIN.depesz.com/s/1yu dauert etwa 4 Minuten, wobei 95% der Zeit für den INDEX-Scan aufgewendet wird.
Mark Davidson
Ich habe vergessen zu erwähnen, dass ich Version 9.1.4 verwende
Mark Davidson
Grundsätzlich ist der Index-Scan recht schnell, das Problem ist, dass er einige Millionen Mal wiederholt wird. Was erhalten Sie, wenn Sie SET enable_nestloop=offdie Abfrage ausführen?
Dekso
-1

Für Anhänger hatte ich ein ähnliches Problem wie

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

Das Problem war, dass meine bigint_column "zwischen x und y" einen Index hatte, aber meine Abfrage war im Grunde "alle Zeilen" in dieser Tabelle, so dass sie nicht den Index verwendete [da sie sowieso die gesamte Tabelle scannen musste], sondern führte einen sequentiellen seq_scan-Scan durch. Eine Lösung für mich war, einen neuen Index für die "mod" -Seite der Gleichung zu erstellen, damit dieser für einen Ausdruck verwendet werden kann .

Rogerdpack
quelle
Downvoters können gerne Kommentare hinterlassen, warum :)
Rogerdpack