Langsame Abfrageleistung aufgrund temporärer Datei?

7

Hier ist die Abfrage:

SELECT "products".* 
FROM "products"
WHERE (status > 100) 
AND "products"."above_revenue_average" = 't' 
AND ("products"."category_id" NOT IN (5))
ORDER BY "products"."start_date" DESC 

Ich habe einen Index für statusund start_date.

Jedes Mal, wenn ich die Abfrage in meiner Anwendung ausführe, wird in den Protokollen Folgendes angezeigt:

[WHITE] temporary file:
path "pg_tblspc/16386/PG_9.3_201306121/pgsql_tmp/pgsql_tmp2544.0", size 37093376 
Query: SELECT "products".* FROM "products"  WHERE (status > 100) 
AND "products"."above_revenue_average" = 't' 
AND ("products"."category_id" NOT IN (5))  ORDER BY "products"."start_date" DESC 

Ich glaube, diese temporäre Dateierstellung ist die Ursache für die langsame Leistung.

Beim Ausführen eines EXPLAIN ANALYZEbekomme ich folgende Ergebnisse:

QUERY PLAN
 Sort  (cost=63395.28..63403.51 rows=16460 width=524)
       (actual time=524.134..562.635 rows=65294 loops=1)
   Sort Key: start_date
   Sort Method: external merge  Disk: 36224kB
   ->  Bitmap Heap Scan on products  
        (cost=4803.40..60389.73 rows=16460 width=524)
        (actual time=27.390..397.879 rows=65294 loops=1)
         Recheck Cond: (status > 100)
         Filter: (above_revenue_average AND (category_id <> 5))
         Rows Removed by Filter: 25115
         ->  Bitmap Index Scan on index_products_on_status
            (cost=0.00..4802.58 rows=89662 width=0) 
            (actual time=18.006..18.006 rows=90411 loops=1)
               Index Cond: (status > 100)
 Total runtime: 577.870 ms
(10 rows)

Ich habe dann http://explain.depesz.com/ verwendet , um es ein bisschen lesbarer zu machen:

Statistiken pro Knotentyp

+-------------------+-------+--------------+------------+
|     node type     | count | sum of times | % of query |
+-------------------+-------+--------------+------------+
| Bitmap Heap Scan  |     1 | 379.873 ms   | 67.5 %     |
| Bitmap Index Scan |     1 | 18.006 ms    | 3.2 %      |
| Sort              |     1 | 164.756 ms   | 29.3 %     |
+-------------------+-------+--------------+------------+

Pro Tabelle Statistiken

+------------------+------------+--------------+------------+
|    Table name    | Scan count |  Total time  | % of query |
+------------------+------------+--------------+------------+
| scan type        | count      | sum of times | % of table |
| products         | 1          | 379.873 ms   | 67.5 %     |
| Bitmap Heap Scan | 1          | 379.873 ms   | 100.0 %    |
+------------------+------------+--------------+------------+

Kann ich die Datenbankleistung durch Hinzufügen weiterer Indizes erhöhen? Vielleicht einige zusammengesetzte? Irgendwelche Ideen?

bnussey
quelle
Hinweis, Cross- Posting
Craig Ringer
2
Können Sie versuchen, den Befehl "set work_mem = '64MB'" auszuführen und ihn dann zu analysieren?
Sahap Asci
Hey @SahapAsci Ich habe das versucht, aber Heroku Standard 0 PostgreSQL setzt diesen Wert jedes Mal zurück
bnussey
Aber wenn ich diese Analyse durchführe, nachdem ich sie auf 64 MB eingestellt habe, bekomme ich eine fast 300 ms Verbesserung! Das ist großartig. Irgendwelche Ideen, wie ich diese Änderung erzwingen kann?
Bnussey
EXPLAIN (BUFFERS, ANALYZE)Erzeugt weitere für den Fall relevante Informationen (wie in den Tag-Informationen für [postgresql-performance] angegeben . Bitte geben Sie immer Ihre Version von Postgres an. Außerdem sagt uns eine einzelne Abfrage nicht, welche Ihrer Prädikate ( WHEREBedingungen) unveränderlich sind und welche variieren (Auf welche Weise?) Wir können nicht ernsthaft raten, wie Indizes ohne weitere Informationen optimiert werden können.
Erwin Brandstetter

Antworten:

4

work_mem

Offensichtlich wird der Sortiervorgang auf die Festplatte übertragen:

Sort Method: external merge  Disk: 36224kB

Weitere work_memkönnen bei der Abfrage helfen, wie bereits von @Kassandry vorgeschlagen . Erhöhen Sie die Einstellung, bis Sie Memorystatt Diskin der EXPLAINAusgabe sehen. Aber es ist wahrscheinlich eine schlechte Idee, die allgemeine Einstellung basierend auf einer Abfrage zu erhöhen. Die richtige Einstellung hängt vom verfügbaren RAM und Ihrer vollständigen Situation ab. Beginnen Sie mit dem Lesen im Postgres-Wiki .

Um Ihre Abfrage nur zu korrigieren, stellen Sie work_memhoch genug für Ihre Transaktion nur mit SET LOCALin derselben Transaktion ein .

BEGIN;

SET LOCAL work_mem = '45MB';

SELECT ...

COMMIT;   -- or ROLLBACK

Sie benötigen wahrscheinlich etwas mehr als 40 MB. Die In-Memory-Darstellung ist etwas größer als die Darstellung auf der Festplatte. Verbunden:

Abfrage

Ihre Anfrage (nach dem Trimmen von Rauschen):

SELECT * 
FROM   products
WHERE  status > 100
AND    above_revenue_average  -- boolean can be used directly
AND    category_id <> 5
ORDER  BY start_date DESC;

Ihre Reihen sind ein halbes Kilobyte breit ( width=524). Müssen Sie alle Spalten zurückgeben? (In der Regel nicht.) Listen Sie nur Spalten in der SELECTListe auf, die Sie aus Ihrer Abfrage benötigen, um die Gesamtleistung zu verbessern, insbesondere da Sie bereits work_memProbleme haben .

Kann eine der beteiligten Spalten NULL sein? Besonders wichtig für category_idund start_date. Vielleicht möchten Sie sich in diesem Fall anpassen ...

Index

Ein mehrspaltiger Index kann sicherlich die Leistung verbessern. ( Wie @Paul umrissen ). Sie müssen Kosten und Gewinn abwägen. Wenn die Leistung für diese Abfrage wichtig oder sehr häufig ist, wählen Sie sie aus. Erstellen Sie nicht für jede Abfrage einen speziellen Index. So wenige wie möglich, so viele wie nötig. Indizes sind leistungsfähiger, wenn sie gemeinsam genutzt werden. Dies erhöht die Wahrscheinlichkeit, dass mehr von ihnen im Cache verbleiben.

Eine booleanSpalte wie above_revenue_averageist ein typischer Kandidat für eine Bedingung in einem Teilindex und nicht für eine Indexspalte.

Meine wilde Vermutung basiert auf unvollständigen Informationen:

CREATE INDEX prod_special_idx ON products (start_date DESC)
WHERE  above_revenue_average
AND    status > 100
AND    category_id <> 5;

Verwenden Sie DESC NULLS LASTin Index- und Abfrage wenn start_dateNULL sein kann.

Erwin Brandstetter
quelle
2

Abhängig davon, wie selektiv die kombinierten Prädikate sind, würde ich mir einen guten Index für diese bestimmte Abfrage vorstellen:

CREATE INDEX index_name
ON products
    (above_revenue_average ASC, start_date DESC)
WHERE
    status > 100
    AND category_id <> 5;

Dies SELECT *ist möglicherweise problematisch, da der obige Index nicht alle Spalten enthält. Wenn der Index dadurch nicht vom Planer ausgewählt wird, können Sie zuerst nur die Primärschlüsselspalten abrufen und dann zur Tabelle zurückkehren, um die zusätzlichen Spalten nur für die ausgewählten Schlüssel zu sammeln.

Paul White 9
quelle
2

Eine der einfachsten und effektivsten Möglichkeiten, die Leistung bei dieser Art von Abfrage zu steigern, besteht darin, die Abfrage auszuführen SET work_mem=40MB(da Sie ~ 32 MB temporäre Datei zum Sortieren haben und ein wenig mehr hilft) und dann abzufragen, ob sich der EXPLAIN ANALYZEPlan ändert Festplatte auf eine In-Memory-Sortierung. Führen Sie anschließend aus RESET work_mem, um den Wert auf den Standardwert in Ihrem zurückzusetzen postgresql.conf.

Die Grundidee wird unter work_mem unter folgendem Link behandelt: Optimieren Ihres PostgreSQL-Servers . Das Ändern dieser Einstellung für eine einzelne Abfrage sollte Ihnen die Vorteile bieten und die genannten Nachteile vermeiden.

Ich hoffe, das hilft. =)

Kassandry
quelle
Hey, vielen Dank! Das funktioniert sicherlich und es geht zurück zu einer In-Memory-Sortierung, aber leider setzt Heroku das work_mem immer wieder zurück. Kennen Sie jemanden, der es erzwingen kann, oder einen Index, der dies helfen könnte? Danke
bnussey
Wenn Sie SET work_mem=40MBden Anfang der Transaktion in Ihre Anwendung einfügen, gilt diese für jeden Lauf dieser bestimmten Abfrage, unabhängig davon, ob Heroku sie am Ende zurücksetzt oder nicht. Ich bin mir nicht sicher, ob es eine Konfigurationsoption für Heroku gibt, mit der Sie work_memleider für Ihre gesamte Konfiguration festlegen können .
Kassandry