Sortierleistung in PostgreSQL verbessern?

7

Ich habe eine einfache Blog-Datenbank in postgres-8.4, die zwei Tabellen enthält, articlesund comments. Ich habe eine Abfrage (generiert von Django), die den neuesten Artikel vom Typ 'NEWS' abrufen und auch die Anzahl der Kommentare für diesen Artikel ermitteln möchte. Dies geschieht mit der folgenden Abfrage:

SELECT "articles"."id", "articles"."datestamp", "articles"."title", "articles"."shorttitle", "articles"."description", "articles"."markdown", "articles"."body", "articles"."idxfti", "articles"."published", "articles"."type", COUNT("comments"."id") AS "comment__count"
FROM "articles"
LEFT OUTER JOIN "comments" ON ("articles"."id" = "comments"."article_id")
WHERE ("articles"."type"='NEWS')
GROUP BY "articles"."id", "articles"."datestamp", "articles"."title", "articles"."shorttitle", "articles"."description", "articles"."markdown", "articles"."body", "articles"."idxfti", "articles"."published", "articles"."type"
ORDER BY "articles"."datestamp" DESC
LIMIT 1;

Keine dieser Tabellen ist besonders groß, und dennoch dauert diese Abfrage 46 ms. Der Ausführungsplan lautet:

Limit  (cost=119.54..119.58 rows=1 width=1150) (actual time=46.479..46.481 rows=1 loops=1)
   ->  GroupAggregate  (cost=119.54..138.88 rows=455 width=1150) (actual time=46.475..46.475 rows=1 loops=1)
     ->  Sort  (cost=119.54..120.68 rows=455 width=1150) (actual time=46.426..46.428 rows=2 loops=1)
           Sort Key: articles.datestamp, articles.id, articles.title, articles.shorttitle, articles.description, articles.markdown, articles.body, articles.idxfti, articles.published, articles.type
           Sort Method:  quicksort  Memory: 876kB
           ->  Hash Left Join  (cost=11.34..99.45 rows=455 width=1150) (actual time=0.513..2.527 rows=566 loops=1)
                 Hash Cond: (articles.id = comments.article_id)
                 ->  Seq Scan on articles  (cost=0.00..78.84 rows=455 width=1146) (actual time=0.017..0.881 rows=455 loops=1)
                       Filter: ((type)::text = 'NEWS'::text)
                 ->  Hash  (cost=8.93..8.93 rows=193 width=8) (actual time=0.486..0.486 rows=193 loops=1)
                       ->  Seq Scan on comments  (cost=0.00..8.93 rows=193 width=8) (actual time=0.004..0.252 rows=193 loops=1)
 Total runtime: 46.574 ms

In der Artikeltabelle ist (unter anderem) folgender Index definiert:

idx_articles_datestamp" btree (datestamp DESC) CLUSTER

Bevor ich es gruppierte, entsprach die Ausführung der Abfrage eher den Schätzungen (ca. 119 ms).

Für mein ungeübtes Auge sieht es so aus, als würde die Sorte hier am meisten Zeit in Anspruch nehmen. Es scheint auch auf allen GROUP zu versuchen , von Feldern zu sortieren, das Problem ist , dass es versucht , auf drei relativ große Felder zu sortieren, body, markdownund idx_fti.

Meine Frage lautet: Ist dies eine unangemessene Zeit, die diese Abfrage in Anspruch nimmt, oder gibt es etwas Offensichtliches, das ich verpasst habe, um diese Abfrage zu beschleunigen? Alle anderen von dieser Blog-Site angeforderten Abfragen benötigen ca. 1-5 ms, um ausgeführt zu werden. Daher dauert diese Abfrage sehr lange. Ich schätze, dass es einen OUTER JOIN und einen SORT gibt, die nicht wirklich helfen. Ich bin jedoch kein Experte. Wenn also jemand Vorschläge hat, wäre das äußerst nützlich.

wachsen
quelle

Antworten:

9

Warum ist es langsam?

Ich würde empfehlen, die Abfrage @ypercube in Kombination mit den genannten Indizes zu verwenden. Aber warum ist die Abfrage, die Sie hatten, im Vergleich so langsam?

Sie haben Ihre Tabellendefinition nicht angegeben, aber ich gehe aus den Spaltennamen und dem, was Sie geschrieben haben, davon aus, dass Sie mehrere (große) Zeichentypspalten ( textoder varchar) in der Tabelle haben articles:

title, shorttitle, description, markdown, body, idx_fti

Ich gehe weiterhin davon aus, dass Sie Ihre Datenbank mit einem anderen Gebietsschema als ausführenC . Das Sortieren großer Textspalten nach einem Gebietsschema ist ziemlich teuer. Das Relevante ist die Zusammenstellung . Überprüfen Sie Ihre (aktuelle) Einstellung auf LC_COLLATE:

SHOW LC_COLLATE;

Mit Postgres 9.1 oder höher können Sie eine Kollatierung auswählen, um Ihre Ausdrücke ad hoc auszuwerten . Mit PostgreSQL 8.4 wird dies jedoch zum Zeitpunkt der Clustererstellung festgelegt und kann später nicht mehr geändert werden.

Wir hatten kürzlich eine verwandte Frage zu SO, bei der wir nach vielen Überlegungen und Tests festgestellt haben, dass das Sortieren nach einem Gebietsschema die größte Verlangsamung darstellt:

Ich erwarte, dass die Abfrage von @ ypercube dieses Problem radikal löst: Nein, GROUP BYdenn die langen Textspalten eliminieren die teure Sortierung insgesamt. Problem gelöst.

Erwin Brandstetter
quelle
Könnten Sie mir bitte bei einer ähnlichen Frage helfen? stackoverflow.com/questions/59818667/…
Ankur Mahajan
8

Eine andere Möglichkeit, die Abfrage mit einer Inline-Unterabfrage neu zu schreiben:

SELECT id,
       datestamp,
       title,
       shorttitle,
       description,
       markdown,
       body,
       idxfti,
       published,
       type,
       ( SELECT COUNT(*) 
         FROM comments 
         WHERE articles.id = comments.article_id
       ) AS comment__count
FROM articles 
WHERE type = 'NEWS'
ORDER BY datestamp DESC 
LIMIT 1
ypercubeᵀᴹ
quelle
1
+1 Dies sollte die beste Lösung sein. In Verbindung mit einem Index an comments(article)und dem oben genannten Index an articles(type, datestamp DESC). Die Abfrage wird verwendet LIMIT 1, daher sollten nur relevante Zeilen abgerufen articleund gezählt werden.
Erwin Brandstetter
3

Wenn articles.type weniger als 10% der Tabelle ausmacht, können Sie von einem Index für diese Spalte profitieren. Sie können mit ziemlicher Sicherheit von einem Index für comment.article_id profitieren, falls Sie noch keinen haben.

Wenn Sie die Kostenfaktoren in Ihrer Konfiguration nicht angepasst haben, können Sie außerdem versuchen, sie random_page_costauf einen Wert zwischen 1,0 und 2,0 zu senken . wenn Ihre aktive Datensatz vollständig zwischengespeichert wird, sollen Sie wahrscheinlich , dass nehmen und seq_page_cost bis zu 0,1. Sie sollten wahrscheinlich cpu_tuple_costauf einen Wert zwischen 0,03 und 0,05 ansteigen . effective_cache_sizesollte die Summe von shared_buffersund was auch immer Ihr Betriebssystem als Cache-Speicherplatz anzeigt.

kgrittn
quelle
Ich hatte bereits einen Index comments.article_id, und das Hinzufügen eines Index articles.typeschien nicht viel zu bewirken. Ich werde versuchen, einige der Servereinstellungen zu optimieren, danke dafür.
wachsen
2

Möglicherweise möchten Sie versuchen, die Gruppe durch zu entfernen und eine Fensterfunktion zum Zählen zu verwenden. Dadurch entfällt die Notwendigkeit, alle Spalten nach / sort zu gruppieren:

SELECT articles.id,
       articles.datestamp,
       articles.title,
       articles.shorttitle,
       articles.description,
       articles.markdown,
       articles.body,
       articles.idxfti,
       articles.published,
       articles.type,
       COUNT(comments.id) over () AS comment__count
FROM articles 
  LEFT OUTER JOIN comments ON (articles.id = comments.article_id)
WHERE (articles.type = 'NEWS')
ORDER BY articles.datestamp DESC 
LIMIT 1
ein Pferd ohne Name
quelle
Ein Index auf (type ASC, datestamp DESC)würde die Leistung zusätzlich verbessern, nicht wahr?
Ypercubeᵀᴹ
@ypercube: Ja, könnte auch sehr gut helfen, den Where-Zustand zu beschleunigen.
a_horse_with_no_name
Ja, wenn die Abfrage umgeschrieben werden kann, gibt es eine Reihe guter Alternativen, von denen dies möglicherweise die beste ist. In Version 9.1 gibt es noch mehr Optionen. Wenn Sie GROUP BYdie Primärschlüsselspalte (n) haben, müssen Sie keine der anderen Spalten aus dieser Tabelle einschließen.
kgrittn
Ich habe versucht, einen Index mit hinzuzufügen, create index idx_articles_type_datestamp on articles(type ASC, datestamp DESC)aber das schien keinen großen Unterschied zu machen. Ich werde sehen, was ich tun kann, um Django zu einem zu zwingen COUNT OVER, aber habe ich Recht, wenn ich denke, dass das Pg-spezifisch ist?
wachsen
3
@growse: Fensterfunktionen sind Standard-SQL und werden von vielen modernen DBMS (Oracle, PostgreSQL, DB2, SQL Server, Teradata, Firebird 3.0) unterstützt
a_horse_with_no_name
-2

Vielleicht könnten Sie einige Tests durchführen, um den Wert von work_mem zu ändern . Dort finden Sie Anweisungen, wie viel Speicher für die Sortiervorgänge verwendet wird.

jap1968
quelle
Wenn eine Sortierung nicht in work_mem passt, wird sie auf die Festplatte übertragen. In diesem Fall wird dies im Abfrageplan angezeigt. Andererseits benötigte die Sortierung hier 876 kB Speicher. Es ist sehr unwahrscheinlich, dass dies größer ist als das work_mem relativ neuer PG-Instanzen.
Dekso