2016-04-26 8 views
2

Ich habe zwei Tabellen: campaign und stats. stats Tabelle enthält tägliche Statistiken, die ich für jede Kampagne sammeln möchte, nichts Außergewöhnliches.Warum ignoriert Postgres alle meine Indizes für bedingte Joins?

Ich indizierte alle Felder, die ich mir vorstellen kann, aber keiner der Indizes wird von dem verwendet, was ich sagen kann. Ich weiß, dass Postgres sich dafür entscheiden könnte, keinen Index zu verwenden, aber es sieht immer noch verdächtig aus, und die Abfrage ist auch nicht blitzschnell. Wie kann ich helfen?

EXPLAIN ANALYZE SELECT "campaign"."id", "campaign"."name", "campaign"."status", SUM("stats"."impressions") AS "impressions" 
    FROM "campaign" 
    LEFT OUTER JOIN "stats" ON 
     ("stats"."date" >= '2016-03-27'::date) 
     AND ("stats"."date" <= '2016-04-25'::date) 
     AND ("campaign"."id" = "stats"."campaign_id") 
    GROUP BY "campaign"."id" 
    ORDER BY "campaign"."status" ASC, "campaign"."created" DESC 
    LIMIT 25; 

Abfrage-Plan:

Limit (cost=6445.26..6445.32 rows=25 width=53) (actual time=642.134..642.422 rows=25 loops=1) 
    -> Sort (cost=6445.26..6446.80 rows=617 width=53) (actual time=642.113..642.209 rows=25 loops=1) 
     Sort Key: campaign.status, campaign.created 
     Sort Method: top-N heapsort Memory: 28kB 
     -> HashAggregate (cost=6421.68..6427.85 rows=617 width=53) (actual time=634.619..637.342 rows=617 loops=1) 
       Group Key: campaign.id 
       -> Hash Right Join (cost=58.88..6269.08 rows=30519 width=53) (actual time=9.986..481.628 rows=31142 loops=1) 
        Hash Cond: (stats.campaign_id = campaign.id) 
        -> Seq Scan on stats (cost=0.00..5790.56 rows=30519 width=8) (actual time=0.044..172.346 rows=31027 loops=1) 
          Filter: ((date >= '2016-03-27'::date) AND (date <= '2016-04-25'::date)) 
          Rows Removed by Filter: 22299 
        -> Hash (cost=51.17..51.17 rows=617 width=49) (actual time=9.325..9.325 rows=617 loops=1) 
          Buckets: 1024 Batches: 1 Memory Usage: 52kB 
          -> Seq Scan on campaign (cost=0.00..51.17 rows=617 width=49) (actual time=0.043..4.490 rows=617 loops=1) 
Planning time: 1.778 ms 
Execution time: 643.217 ms 

Tables:

          Table "public.campaign" 
     Column  |   Type   |       Modifiers       
----------------------+--------------------------+--------------------------------------------------------------- 
id     | integer     | not null default nextval('campaign_id_seq'::regclass) 
name     | character varying(255) | not null 
created    | timestamp with time zone | not null 
status    | character varying(32) | not null 
Indexes: 
    "campaign_pkey" PRIMARY KEY, btree (id) 
    "campaign_9acb4454" btree (status) 
    "campaign_9bea82de" btree (product_id) 
    "campaign_created_7aea656cce4d74c_uniq" btree (created) 
Foreign-key constraints: 
    TABLE "stats" CONSTRAINT "stats_campaign_id_dabb6227_fk_campaign_id" FOREIGN KEY (campaign_id) REFERENCES campaign(id) DEFERRABLE INITIALLY DEFERRED 


             Table "public.stats" 
    Column  |   Type   |       Modifiers       
-----------------+-------------------------+------------------------------------------------------------ 
id    | integer     | not null default nextval('stats_id_seq'::regclass) 
date   | date     | not null 
impressions  | integer     | not null 
campaign_id  | integer     | not null 
Indexes: 
    "stats_pkey" PRIMARY KEY, btree (id) 
    "stats_date_1de4ab17_uniq" btree (date) 
    "stats_f14acec3" btree (campaign_id) 
Foreign-key constraints: 
    "stats_campaign_id_dabb6227_fk_campaign_id" FOREIGN KEY (campaign_id) REFERENCES campaign(id) DEFERRABLE INITIALLY DEFERRED 

===============

Edit:

Abfrageplan, wenn die Bedingung aus JOIN in WHERE verschoben wird:

Limit (cost=10252.48..10252.55 rows=25 width=252) (actual time=921.152..921.423 rows=25 loops=1) 
    -> Sort (cost=10252.48..10254.03 rows=617 width=252) (actual time=921.142..921.230 rows=25 loops=1) 
     Sort Key: campaign.status, campaign.created 
     Sort Method: top-N heapsort Memory: 37kB 
     -> HashAggregate (cost=10161.03..10235.07 rows=617 width=252) (actual time=910.690..916.553 rows=550 loops=1) 
       Group Key: campaign.id 
       -> Hash Right Join (cost=58.88..6575.05 rows=30519 width=252) (actual time=7.655..708.881 rows=31075 loops=1) 
        Hash Cond: (stats.campaign_id = campaign.id) 
        Filter: ((stats.date IS NULL) OR ((stats.date >= '2016-03-27'::date) AND (stats.date <= '2016-04-25'::date))) 
        Rows Removed by Filter: 22299 
        -> Seq Scan on stats (cost=0.00..5526.71 rows=52771 width=56) (actual time=0.009..249.230 rows=53326 loops=1) 
        -> Hash (cost=51.17..51.17 rows=617 width=204) (actual time=7.588..7.588 rows=617 loops=1) 
          Buckets: 1024 Batches: 1 Memory Usage: 128kB 
          -> Seq Scan on campaign (cost=0.00..51.17 rows=617 width=204) (actual time=0.009..3.124 rows=617 loops=1) 
Planning time: 0.604 ms 
Execution time: 922.323 ms 
+0

Nun, ich denke PostgreSQL Lage sein sollte, einen Index zu verwenden auf dem Datumsfeld. Was ist der Anteil von 'stats' ausgewählt mit' ("stats". "Date"> = '2016-03-27' :: date) UND ("stats". "Date" <= '2016-04-25': : Datum) '? Hast du kürzlich Staubsauger? –

+0

@ ClémentPrévost Es sind ungefähr 50% der Datensätze. Wenn ich den Datumsbereich auf 1 Tag ändere, wird das Indexdatum verwendet. Alles klar, nehmen wir an, der Datumsindex ist in Ordnung, warum sonst nichts, warum macht es die Seq-Scan-Kampagne (letzte Zeile im Plan), die am offensichtlichsten zu indexieren scheint? Auch ich sauge es nicht manuell, ich dachte, es sollte automatisch sein. – serg

+0

Es ist automatisch, das war nur um sicherzustellen. Ändert das etwas, wenn Sie das '(" stats "." Date "> = '2016-03-27' :: date) UND (" stats "." Date "<= '2016-04-25' :: Datum) 'aus der Join-Klausel heraus? Etwas wie 'WHERE (stats.date ist Null ODER ((" Statistik "." Datum "> =" 2016-03-27 ":: Datum) UND (" Statistik "." Datum "<=" 2016-04-25 ":: date))." Ich denke, dass die Join-Bedingung zu komplex ist, um PostgreSQL verständlich zu machen, dass der Datumsfilter tatsächlich ein Filter und keine Join-Bedingung ist. –

Antwort

1

Sie betrachten könnte die Abfrage wie folgt zu schreiben:

SELECT c."id", c."name", c."status", 
     (SELECT SUM(s."impressions") 
     FROM "stats" s 
     WHERE c."id" = s."campaign_id" AND 
       s."date" >= '2016-03-27'::date AND 
       s."date" <= '2016-04-25'::date 
     ) as "impressions" 
FROM "campaign" c 
ORDER BY c."status" ASC, c."created" DESC ; 

Dann werden die besten Indizes sind campaign(status, created desc, name, id) und stats(campaign_id, date, impressions). Hinweis: Dies sind beides mehrspaltige Indizes, die die Abfrage vollständig abdecken (dh alle Spalten, auf die zugegriffen wird, befinden sich in den Indizes).

Der Postgres-Optimierer ist gut. Denken Sie jedoch nicht, dass es gut genug ist, um die äußere Aggregation in Ihrer Form der Abfrage zu optimieren. Da diese Version den Index für ORDER BY verwenden kann, ist diese Version, die eine korrelierte Unterabfrage verwendet, möglicherweise schneller als die Versionen, die explizite GROUP BY verwenden.

+0

Ich habe neben den Impressions viele berechnete Statistikspalten, kann ich diese Methode noch verwenden? – serg

+0

@serg. . . Beginnen Sie mit dieser Abfrage und prüfen Sie, ob sie die Leistung verbessert. Wenn ja, versuchen Sie eine seitliche Verbindung. Wenn das immer noch gut funktioniert (und es sollte), dann fügen Sie die zusätzlichen Spalten hinzu. –

+0

Wie füge ich zusätzliche Spalten hinzu, füge eine Unterabfrage pro berechnetem Feld hinzu? Oder ich vermisse das Offensichtliche. Können Sie bitte eine Anfrage für 2 Felder stellen. Oder Sie beabsichtigen, dies in zwei Abfragen durchzuführen. Wählen Sie zuerst alle Kampagnen aus und dann die Statistiken für diese Kampagnen-IDs aus. Dies wäre unpraktisch, wenn ich nach einem Statistikfeld (wie der Summe der Impressionen) bestellen möchte. – serg

1

Wenn Sie zum ersten Mal zu begrenzen, könnten Sie in der Lage sein, die Dinge zu beschleunigen, aber Sie können das nicht tun, wenn Sie wollen auf stats Aggregationen sortieren

WITH top_campaign (
    SELECT * 
    FROM "campaign" 
    ORDER BY "campaign"."status" ASC, "campaign"."created" DESC 
    LIMIT 25 
) 
SELECT "campaign"."id", "campaign"."name", "campaign"."status", SUM("stats"."impressions") AS "impressions" 
FROM "top_campaign" as "campaign" 
LEFT OUTER JOIN "stats" ON ("campaign"."id" = "stats"."campaign_id") AND ("stats"."date" >= '2016-03-27'::date) AND ("stats"."date" <= '2016-04-25'::date) 
GROUP BY "campaign"."id" 
ORDER BY "campaign"."status" ASC, "campaign"."created" DESC 
Verwandte Themen