2014-11-25 15 views
6

Wenn die folgenden Abfragen der Datenbank (Postgres) ausgeführt werden, ist der zweite Aufruf viel schneller.Leistung der ersten Abfrage verbessern

Ich denke, die erste Abfrage ist langsam, da das Betriebssystem (Linux) die Daten von der Festplatte erhalten muss. Die zweite Abfrage profitiert vom Caching auf Dateisystemebene und in Postgres.

Gibt es eine Möglichkeit, die Datenbank zu optimieren, um die Ergebnisse schnell auf dem ersten Anruf zu erhalten?

Erster Aufruf (langsam)

[email protected]:~$ psql 

foo3_bar_p=# explain analyze SELECT "foo3_beleg"."id", ... FROM "foo3_beleg" WHERE 
foo3_bar_p-# (("foo3_beleg"."id" IN (SELECT beleg_id FROM foo3_text where 
foo3_bar_p(# content @@ 'footown'::tsquery)) AND "foo3_beleg"."belegart_id" IN 
foo3_bar_p(# ('...', ...)); 
                          QUERY PLAN                     
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 
Nested Loop (cost=75314.58..121963.20 rows=152 width=135) (actual time=27253.451..88462.165 rows=11 loops=1) 
    -> HashAggregate (cost=75314.58..75366.87 rows=5229 width=4) (actual time=16087.345..16113.988 rows=17671 loops=1) 
     -> Bitmap Heap Scan on foo3_text (cost=273.72..75254.67 rows=23964 width=4) (actual time=327.653..16026.787 rows=27405 loops=1) 
       Recheck Cond: (content @@ '''footown'''::tsquery) 
       -> Bitmap Index Scan on foo3_text_content_idx (cost=0.00..267.73 rows=23964 width=0) (actual time=281.909..281.909 rows=27405 loops=1) 
        Index Cond: (content @@ '''footown'''::tsquery) 
    -> Index Scan using foo3_beleg_pkey on foo3_beleg (cost=0.00..8.90 rows=1 width=135) (actual time=4.092..4.092 rows=0 loops=17671) 
     Index Cond: (id = foo3_text.beleg_id) 
     Filter: ((belegart_id)::text = ANY ('{... 
     Rows Removed by Filter: 1 
Total runtime: 88462.809 ms 
(11 rows) 

Zweiter Aufruf (schnell)

Nested Loop (cost=75314.58..121963.20 rows=152 width=135) (actual time=127.569..348.705 rows=11 loops=1) 
    -> HashAggregate (cost=75314.58..75366.87 rows=5229 width=4) (actual time=114.390..133.131 rows=17671 loops=1) 
     -> Bitmap Heap Scan on foo3_text (cost=273.72..75254.67 rows=23964 width=4) (actual time=11.961..97.943 rows=27405 loops=1) 
       Recheck Cond: (content @@ '''footown'''::tsquery) 
       -> Bitmap Index Scan on foo3_text_content_idx (cost=0.00..267.73 rows=23964 width=0) (actual time=9.226..9.226 rows=27405 loops=1) 
        Index Cond: (content @@ '''footown'''::tsquery) 
    -> Index Scan using foo3_beleg_pkey on foo3_beleg (cost=0.00..8.90 rows=1 width=135) (actual time=0.012..0.012 rows=0 loops=17671) 
     Index Cond: (id = foo3_text.beleg_id) 
     Filter: ((belegart_id)::text = ANY ('... 
     Rows Removed by Filter: 1 
Total runtime: 348.833 ms 
(11 rows) 

Tabelle Layout der foo3_text Tabelle (28M Reihen)

foo3_egs_p=# \d foo3_text 
           Table "public.foo3_text" 
    Column |   Type   |       Modifiers       
----------+-----------------------+------------------------------------------------------------ 
id  | integer    | not null default nextval('foo3_text_id_seq'::regclass) 
beleg_id | integer    | not null 
index_id | character varying(32) | not null 
value | text     | not null 
content | tsvector    | 
Indexes: 
    "foo3_text_pkey" PRIMARY KEY, btree (id) 
    "foo3_text_index_id_2685e3637668d5e7_uniq" UNIQUE CONSTRAINT, btree (index_id, beleg_id) 
    "foo3_text_beleg_id" btree (beleg_id) 
    "foo3_text_content_idx" gin (content) 
    "foo3_text_index_id" btree (index_id) 
    "foo3_text_index_id_like" btree (index_id varchar_pattern_ops) 
Foreign-key constraints: 
    "beleg_id_refs_id_6e6d40770e71292" FOREIGN KEY (beleg_id) REFERENCES foo3_beleg(id) DEFERRABLE INITIALLY DEFERRED 
    "index_id_refs_name_341600137465c2f9" FOREIGN KEY (index_id) REFERENCES foo3_index(name) DEFERRABLE INITIALLY DEFERRED 

Hardware Änderungen (SSD i statt herkömmlicher Festplatten) oder RAM-Disketten möglich. Aber vielleicht kann die aktuelle Hardware auch schnellere Ergebnisse liefern.

Version: PostgreSQL 9.1.2 auf x86_64-unknown-linux-gnu

Bitte einen Kommentar hinterlassen, wenn Sie weitere Informationen benötigen.

+0

Wenn die WHERE-Klausel immer die gleiche ist, was ist mit periodischer Ausgabe der Anfrage? Die Daten heiß zu halten, ist die einzige, die ich kenne, um die Wucht des ersten Versuchs zu vermeiden. Wie pro Optimierung haben Sie die Selektivität von AND "foo3_beleg" überprüft. "Belegart_id". Würde es Sinn machen, es zum ersten SELECT zu verschieben? – SCO

+0

@SCO nein, die WHERE-Klausel ist anders. Der Suchbegriff (hier "Fußzeile") unterscheidet sich. – guettli

+2

Postgres 9.4 wird die 'pg_prewarm' Erweiterung haben, die den Puffer-Cache auf Anfrage füllen kann: http://www.postgresql.org/docs/9.4/static/pgprewarm.html –

Antwort

0

Manchmal kann das Verschieben eines "WHERE x IN" in ein JOIN die Leistung erheblich verbessern. Versuchen Sie folgendes:

SELECT 
    foo3_beleg.id, ... 
FROM 
    foo3_beleg b INNER JOIN 
    foo3_text t ON (t.beleg_id = b.id AND t.content @@ 'footown'::tsquery) 
WHERE 
    foo3_beleg.belegart_id IN ('...', ...); 

hier ein wiederholbares Experiment ist mein Anspruch zu unterstützen.

Ich habe zufällig eine große Postgres-Datenbank (30 Millionen Zeilen) (http://juliusdavies.ca/2013/j.emse/bertillonage/), also habe ich das in Postgres 9.4beta3 geladen.

Die Ergebnisse sind beeindruckend. Der Sub-select Ansatz ist etwa 20-mal langsamer:

time psql myDb < using-in.sql 
real 0m17.212s 

time psql myDb < using-join.sql 
real 0m0.807s 

Für Interessenten an replizieren, hier sind die rohen SQL-Abfragen habe ich meine Theorie zu testen.

Diese Abfrage verwendet eine „SELECT IN“ Unterabfrage, und es ist 20-mal langsamer (17 Sekunden auf meinem Laptop bei der ersten Ausführung):

-- using-in.sql 
    SELECT 
    COUNT(DISTINCT sigsha1re) AS a_intersect_b, infilesha1 
    FROM 
    files INNER JOIN sigs ON (files.filesha1 = sigs.filesha1) 
    WHERE 
    sigs.sigsha1re IN (
     SELECT sigsha1re FROM sigs WHERE sigs.sigsha1re like '0347%' 
    ) 
    GROUP BY 
    infilesha1 

Diese Abfrage bewegt den Zustand aus der Unterabfrage und in die Verbindungskriterien, und es ist 20 mal schneller (0,8 Sekunden auf meinem Laptop bei der ersten Ausführung).

-- using-join.sql 
    SELECT 
    COUNT(DISTINCT sigsha1re) AS a_intersect_b, infilesha1 
    FROM 
    files INNER JOIN sigs ON (
     files.filesha1 = sigs.filesha1 AND sigs.sigsha1re like '0347%' 
    ) 
    GROUP BY 
    infilesha1 

P. S. Wenn Sie neugierig sind, wofür diese Datenbank ist, können Sie damit berechnen, wie ähnlich eine beliebige JAR-Datei für alle JAR-Dateien im Maven-Repository von 2011 ist.

./query.sh lib/commons-codec-1.5.jar | psql myDb 

similarity |      a = 39 = commons-codec-1.5.jar (bin2bin)      
------------+-------------------------------------------------------------------------------------- 
    1.000  | commons-codec-1.5.jar 
    0.447  | commons-codec-1.4.jar 
    0.174  | org.apache.sling.auth.form-1.0.2.jar 
    0.170  | org.apache.sling.auth.form-1.0.0.jar 
    0.142  | jbehave-core-3.0-beta-3.jar 
    0.142  | jbehave-core-3.0-beta-4.jar 
    0.141  | jbehave-core-3.0-beta-5.jar 
    0.141  | jbehave-core-3.0-beta-6.jar 
    0.140  | commons-codec-1.2.jar 
+0

"Manchmal kann das Verschieben von" WHERE x IN "in einen JOIN die Leistung erheblich verbessern." - Sie müssen über MySQL reden. Postgres ist schlau genug, um den Abfragebaum neu zu schreiben, wenn dies möglich ist. Die Frage von OP bezieht sich eindeutig auf ein "Index/Daten ist nicht im Cache noch" Problem. (Und die richtige Antwort ist, ich würde sammeln, bereits in den Kommentaren.) –

+1

Wann wurde Postgres besser darin? Ich stieß auf dieses Problem mit 8.4 und 9.0. Nie auf anderen Versionen versucht. (Und ich hatte ähnliche Symptome: langsame Abfrage zum ersten Mal, viel schneller zum zweiten Mal.) –

+0

Es tut so lange, wie ich Postgres (8.0) verwendet habe; wo sich die Dinge im Laufe der Jahre verbessert haben, war, welche zusätzlichen Klassen von Unterabfragen zusammenbrechen. –

1

mit Julius einverstanden, aber, wenn Sie Sachen von foo3_beleg benötigen, versucht vor statt (und es würde helfen, wenn Sie zu Ihrem SQL eingefügt haben, nicht nur Ihren Plan erklären).

select ... 
from foo3_beleg b 
where exists 
(select 1 from foo_text s where t.beleg_id = b.id) 
.... 

Allerdings vermute ich Ihre „wake up“ auf dem ersten Pass ist nur Ihre db Laden der IN subquery Zeilen in Speicher. Das wird wahrscheinlich passieren, obwohl ein EXISTS im Allgemeinen viel schneller ist als ein IN (INs werden selten benötigt, wenn sie keine hartcodierten Listen enthalten, und eine gelbe Flagge, wenn ich sql überprüfe).

+0

Die Verwendung von wird hier nicht helfen. Die Frage von OP bezieht sich eindeutig auf ein "Index/Daten ist nicht im Cache noch" Problem. (Und die richtige Antwort ist, ich würde sammeln, bereits in den Kommentaren.) –

+0

Nun, wenn Sie bemerkt haben, habe ich ziemlich genau gesagt, dass "... wahrscheinlich passieren, unabhängig ...". Abhängig davon, wie die tatsächlichen Daten aussehen und wie pg beim Laden von Caches/Überprüfungsübereinstimmungen vorgeht, sollten Sie daran denken, dass ein EXISTs schnell ist, da es bei der ersten Übereinstimmung zurückgegeben werden kann. Das mag auch bei einem IN der Fall sein, aber das ist ein Optimierungsaspekt und kein Merkmal des SQL-Befehls. Fazit: Ich bin noch auf einen gültigen Anwendungsfall von IN gestoßen, der nicht EXISTS sein kann. –

2

Postgres bietet Ihnen die Möglichkeit, eine Konfiguration für die Runtime-Abfrage durchzuführen, die ausgeführt wird, um Ihre E/A-Betriebspriorität zu bestimmen.

random_page_cost(floating point) -(reference) ist, was Ihnen helfen kann. Es wird im Grunde Ihr IO/CPU Betriebsverhältnis einstellen.

Höherer Wert bedeutet I/O ist wichtig, ich habe sequentielle Festplatte; und ein niedrigerer Wert bedeutet, dass E/A nicht wichtig ist. Ich habe eine Platte mit wahlfreiem Zugriff.

Standardwert ist 4.0, und möglicherweise möchten Sie erhöhen und testen, ob Ihre Abfrage kürzere Zeit benötigt.

Vergessen Sie nicht, dass Ihre E/A-Priorität von Ihrer Spaltenanzahl und Zeilenanzahl abhängt.

Ein großes ABER; Da Ihre Indizes btree sind, geht Ihre CPU-Priorität viel schneller als die I/O-Prioritäten nach oben. Sie können Komplexität grundsätzlich Prioritäten zuordnen.

CPU Priority = O(log(x)) 
I/O Priority = O(x) 

Alles in allem bedeutet dies, wenn Postgre Wert 4.0 würde für 100k Einträge, Sie legen sollte es (ca.). (4.0 * log(100k) * 10M)/(log(10M) * 100k) für 10M Eintrag.

+0

Es tut mir sehr leid, ich werde das nicht testen können, bevor die Bounty abläuft ... – guettli

+0

kein Problem. ich hoffe es hilft :) – totten

1

Wenn Sie die Abfrage zum ersten Mal ausführen, lädt Postgres die Daten von der Festplatte, die selbst bei einer guten Festplatte langsam ist. Wenn Sie Ihre Abfrage das zweite Mal ausführen, lädt sie die zuvor geladenen Daten aus dem RAM, was natürlich schneller ist.

Die Lösung für dieses Problem wäre es, Beziehungsdaten entweder in den Betriebssystempuffer-Cache oder dem PostgreSQL Puffercache mit Last:

int8 pg_prewarm(regclass, mode text default 'buffer', fork text default 'main', first_block int8 default null, last_block int8 default null): zu vorgewärmtem

Das erste Argument ist die Relation . Das zweite Argument ist die zu verwendende Vorwärmungsmethode, wie weiter unten diskutiert wird; der dritte ist die zu heizende Beziehungsgabel, meist Haupt. Das vierte Argument ist die erste Blocknummer, die vorgewärmt wird (NULL wird als Synonym für Null akzeptiert). Das fünfte Argument ist die letzte Blocknummer, die vorgewärmt wird (NULL bedeutet Vorwarm durch den letzten Block in der Relation). Der Rückgabewert ist die Anzahl der vorgewärmten Blöcke.

Es gibt drei verfügbare Vorwärmmethoden. prefetch gibt asynchrone Prefetch-Anfragen an das Betriebssystem aus, falls dies unterstützt wird, oder gibt sonst einen Fehler aus. Lesen liest den angeforderten Bereich von Blöcken; Anders als beim Prefetch ist dies synchron und wird auf allen Plattformen und Builds unterstützt, kann jedoch langsamer sein. Puffer liest den angeforderten Bereich von Blöcken in den Datenbankpuffercache.Beachten Sie, dass bei Versuchen mit diesen Methoden der Versuch, mehr Blöcke vorzuwärmen als zwischengespeichert werden kann - vom Betriebssystem bei Verwendung von prefetch oder read oder von PostgreSQL bei Verwendung von buffer - dazu führt, dass Blöcke mit niedrigerer Nummerierung als höher deklariert werden numerierte Blöcke werden eingelesen. Vorgewärmte Daten genießen auch keinen besonderen Schutz vor Cache-Räumungen, daher ist es möglich, dass andere Systemaktivitäten die neu vorgewärmten Blöcke kurz nach dem Lesen entfernen können; umgekehrt kann das Vorwärmen auch andere Daten aus dem Cache entfernen. Aus diesen Gründen ist die Vorwärmung in der Regel beim Start am nützlichsten, wenn Caches weitgehend leer sind.

Source

Hope this geholfen!