Ich habe die folgende Tabelle mit ungefähr 175.000 Datensätzen:
Column | Type | Modifiers
----------------+-----------------------------+-------------------------------------
id | uuid | not null default uuid_generate_v4()
competition_id | uuid | not null
user_id | uuid | not null
first_name | character varying(255) | not null
last_name | character varying(255) | not null
image | character varying(255) |
country | character varying(255) |
slug | character varying(255) | not null
total_votes | integer | not null default 0
created_at | timestamp without time zone |
updated_at | timestamp without time zone |
featured_until | timestamp without time zone |
image_src | character varying(255) |
hidden | boolean | not null default false
photos_count | integer | not null default 0
photo_id | uuid |
Indexes:
"entries_pkey" PRIMARY KEY, btree (id)
"index_entries_on_competition_id" btree (competition_id)
"index_entries_on_featured_until" btree (featured_until)
"index_entries_on_hidden" btree (hidden)
"index_entries_on_photo_id" btree (photo_id)
"index_entries_on_slug" btree (slug)
"index_entries_on_total_votes" btree (total_votes)
"index_entries_on_user_id" btree (user_id)
und ich führe die folgende Abfrage aus, um den Rang des Eintrags und den Slug des nächsten und vorherigen Eintrags zu erhalten:
WITH entry_with_global_rank AS (
SELECT id
, rank() OVER w AS global_rank
, LAG(slug) OVER w AS previous_slug
, LEAD(slug) OVER w AS next_slug
FROM entries
WHERE competition_id = 'bdd94eee-25a4-481f-b7b5-37aaed953c6b'
WINDOW w AS (PARTITION BY competition_id ORDER BY total_votes DESC)
)
SELECT *
FROM entry_with_global_rank
WHERE id = 'f2df68b7-d720-459d-8c4d-d11e28e0f0c0'
LIMIT 1;
Hier sind die Ergebnisse von EXPLAIN
:
QUERY PLAN
-----------------------------------------------------------------------------------------------
Limit (cost=516228.88..516233.37 rows=1 width=88)
CTE entry_with_global_rank
-> WindowAgg (cost=510596.59..516228.88 rows=250324 width=52)
-> Sort (cost=510596.59..511222.40 rows=250324 width=52)
Sort Key: entries.total_votes
-> Seq Scan on entries (cost=0.00..488150.74 rows=250324 width=52)
Filter: (competition_id = 'bdd94eee-25a4-481f-b7b5-37aaed953c6b'::uuid)
-> CTE Scan on entry_with_global_rank (cost=0.00..5632.29 rows=1252 width=88)
Filter: (id = 'f2df68b7-d720-459d-8c4d-d11e28e0f0c0'::uuid)
(9 rows)
Diese Abfrage dauert ca. 1400 ms. Gibt es eine Möglichkeit, dies zu beschleunigen?
Bearbeiten:
Hier sind die Ergebnisse von EXPLAIN ANALYZE
:
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=516228.88..516233.37 rows=1 width=88) (actual time=1232.824..1232.824 rows=1 loops=1)
CTE entry_with_global_rank
-> WindowAgg (cost=510596.59..516228.88 rows=250324 width=52) (actual time=1202.101..1226.846 rows=8727 loops=1)
-> Sort (cost=510596.59..511222.40 rows=250324 width=52) (actual time=1202.069..1213.992 rows=8728 loops=1)
Sort Key: entries.total_votes
Sort Method: quicksort Memory: 8128kB
-> Seq Scan on entries (cost=0.00..488150.74 rows=250324 width=52) (actual time=89.970..1174.083 rows=50335 loops=1)
Filter: (competition_id = 'bdd94eee-25a4-481f-b7b5-37aaed953c6b'::uuid)
Rows Removed by Filter: 125477
-> CTE Scan on entry_with_global_rank (cost=0.00..5632.29 rows=1252 width=88) (actual time=1232.822..1232.822 rows=1 loops=1)
Filter: (id = 'f2df68b7-d720-459d-8c4d-d11e28e0f0c0'::uuid)
Rows Removed by Filter: 8726
Total runtime: 1234.424 ms
(13 rows)
Bearbeiten 2:
Ich habe VACUUM ANALYZE
die Datenbank ausgeführt und jetzt hat sich die Abfragezeit verbessert, obwohl ich sicher bin, dass es eine Möglichkeit geben muss, die Leistung zu verbessern:
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=475372.26..475376.76 rows=1 width=88) (actual time=138.388..138.388 rows=1 loops=1)
CTE entry_with_global_rank
-> WindowAgg (cost=470662.23..475372.26 rows=209335 width=35) (actual time=125.489..132.214 rows=4178 loops=1)
-> Sort (cost=470662.23..471185.56 rows=209335 width=35) (actual time=125.462..126.724 rows=4179 loops=1)
Sort Key: entries.total_votes
Sort Method: quicksort Memory: 5510kB
-> Bitmap Heap Scan on entries (cost=71390.90..452161.77 rows=209335 width=35) (actual time=29.381..87.130 rows=50390 loops=1)
Recheck Cond: (competition_id = 'bdd94eee-25a4-481f-b7b5-37aaed953c6b'::uuid)
-> Bitmap Index Scan on index_entries_on_competition_id (cost=0.00..71338.56 rows=209335 width=0) (actual time=23.593..23.593 rows=51257 loops=1)
Index Cond: (competition_id = 'bdd94eee-25a4-481f-b7b5-37aaed953c6b'::uuid)
-> CTE Scan on entry_with_global_rank (cost=0.00..4710.04 rows=1047 width=88) (actual time=138.387..138.387 rows=1 loops=1)
Filter: (id = '9470ec4f-fed1-4f95-bbed-1e3dbba5f53b'::uuid)
Rows Removed by Filter: 4177
Total runtime: 138.588 ms
(14 rows)
Edit 3:
Wie gewünscht, wird der endgültige Abfrageplan mit dem Deckungsindex direkt nach einem VACUUM ANALYZE
:
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=0.42..6771.99 rows=1 width=88) (actual time=46.765..46.765 rows=1 loops=1)
-> Subquery Scan on entry_with_global_rank (cost=0.42..6771.99 rows=1 width=88) (actual time=46.763..46.763 rows=1 loops=1)
Filter: (entry_with_global_rank.id = 'f2df68b7-d720-459d-8c4d-d11e28e0f0c0'::uuid)
Rows Removed by Filter: 9128
-> WindowAgg (cost=0.42..5635.06 rows=90955 width=35) (actual time=0.090..40.002 rows=9129 loops=1)
-> Index Only Scan using entries_extra_special_idx on entries (cost=0.42..3815.96 rows=90955 width=35) (actual time=0.071..10.973 rows=9130 loops=1)
Index Cond: (competition_id = 'bdd94eee-25a4-481f-b7b5-37aaed953c6b'::uuid)
Heap Fetches: 166
Total runtime: 46.867 ms
(9 rows)
competition_id = 'bdd94eee-25a4-481f-b7b5-37aaed953c6b'
. Verwenden Sie EXPLAIN ANALYZE, um die tatsächlichen Zählwerte zu erhalten. Die Selektivität dieser bestimmten ID ist hier entscheidend.PARTITION BY competition_id
da es nur einen Wert gibt (aufgrund derWHERE
Klausel).Antworten:
Der CTE wird hier nicht benötigt und stellt eine Optimierungsbarriere dar. Eine einfache Unterabfrage bietet im Allgemeinen eine bessere Leistung:
Wie @Daniel kommentierte , habe ich die
PARTITION BY
Klausel aus der Fensterdefinition entfernt, da Sie sichcompetition_id
sowieso auf eine einzelne beschränken .Tabellenlayout
Sie können Ihr Tabellenlayout optimieren, um die Speichergröße auf der Festplatte geringfügig zu reduzieren, wodurch alles noch etwas schneller wird:
Mehr dazu:
Auch, Sie tatsächlich benötigen alle diese
uuid
Spalten?int
oderbigint
wird nicht für dich arbeiten? Würde Tabelle und Indizes etwas kleiner und alles schneller machen.Und ich würde nur
text
für die Zeichendaten verwenden, aber das wird die Leistung der Abfrage nicht verbessern.Nebenbei:
character varying(255)
ist in Postgres fast immer sinnlos. Einige andere RDBMS profitieren von der Beschränkung der Länge, für Postgres ist alles gleich (es sei denn, Sie müssen tatsächlich die unwahrscheinliche maximale Länge von 255 Zeichen erzwingen).Sonderindex
Schließlich könnten Sie einen hochspezialisierten Index erstellen (nur wenn die Indexpflege das spezielle Gehäuse wert ist):
Das Hinzufügen
(id, slug)
zum Index ist nur dann sinnvoll, wenn Sie nur Index-Scans erhalten können. (Deaktiviertes Autovakuum oder viele gleichzeitige Schreibvorgänge würden diesen Aufwand zunichte machen.) Entfernen Sie andernfalls die letzten beiden Spalten.Überprüfen Sie dabei Ihre Indizes. Sind sie alle in Gebrauch? Hier könnte es tote Fracht geben.
quelle
entries_special_idx
verringert und die hinzugefügt, und jetzt läuft die Abfrage in ~ 40 ms!Index Only
Scans in derANALYZE
Ausgabe? Würde es Ihnen etwas ausmachen, der Frage zum Vergleich einen weiteren Abfrageplan hinzuzufügen?