Am 19.02.22 um 15:21 schrieb Erik Schanze:
Am 19.02.22 um 14:52 schrieb Sebastian Weckend:
Deswegen dauert es wahrscheinlich so lang, da ohne Index für die
Sortierung alle Zeilen angeschaut werden müssen. Entweder solltest du
für den Timestamp einen Index hinzufügen (was den aber unique machen
dürfte, also u.U. nicht gewollt) oder einen primary key hinzufügen,
nach dem du sortieren kannst.


Sorry, da bin ich schon raus. ;-)

Der Anwendungsfall hier ist ein Logger, der alle 2 Minuten
Stromzählerwerte in die Datenbank schreibt. Zum Zugriff auf die
Tabellenwerte wird immer der Timestamp genutzt, also alle Werte eines
bestimmten Tages oder eines Monats etc. aus der Datenbank geholt und
dann in PHP in Diagrammen dargestellt.

Wie hilft mir da ein Index? Weitere Spalten wollte ich vermeiden, um die
Datenmenge nicht unnötig aufzublähen.



Wenn Du alle Werte eines Tages holen willst, hilft ein Index schon mal ganz gewaltig. Ich hab das mal nachgestellt. PostgreSQL 14.1 in einer lokalen Minikube-Kubernetes-Instanz, daher nicht unbedingt schnell.


test=# create table logger(created timestamp, c180 bigint, c280 bigint);
CREATE TABLE
test=# insert into logger select '2000-01-01'::timestamp + s * '1minute'::interval, random()*100000, random()*100000 from generate_series(1,10000000) s;
INSERT 0 10000000

test=# select min(created), max(created) from logger;
         min         |         max
---------------------+---------------------
 2000-01-01 00:01:00 | 2019-01-05 10:40:00
(1 row)



Das sind also 10.000.000 Datenpunkte, minütlicher Abstand, nach ca. 9 Jahren.


test=# explain analyse select created, c180, c180 - lag(c180) over (order by created), c280, c280 - lag(c280) over (order by created) from logger;
                                                             QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------
 WindowAgg  (cost=1736550.18..1986553.06 rows=10000115 width=40) (actual time=88513.167..225937.881 rows=10000000 loops=1)    ->  Sort  (cost=1736550.18..1761550.47 rows=10000115 width=24) (actual time=88513.112..131629.556 rows=10000000 loops=1)
         Sort Key: created
         Sort Method: external merge  Disk: 332768kB
         ->  Seq Scan on logger  (cost=0.00..163696.15 rows=10000115 width=24) (actual time=0.132..43941.856 rows=10000000 loops=1)
 Planning Time: 0.818 ms
 Execution Time: 267222.240 ms
(7 rows)


Man sieht, es dauert. Und: es muß zum Sortieren temp. auf Platte schreiben (Sort Method: external merge  Disk: 332768kB).


Jetzt mit Index:

test=# create index idx_logger on logger(created);
CREATE INDEX
test=# explain analyse select created, c180, c180 - lag(c180) over (order by created), c280, c280 - lag(c280) over (order by created) from logger;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------
 WindowAgg  (cost=0.43..548386.44 rows=10000000 width=40) (actual time=0.302..142101.995 rows=10000000 loops=1)    ->  Index Scan using idx_logger on logger (cost=0.43..323386.43 rows=10000000 width=24) (actual time=0.270..45946.164 rows=10000000 loops=1)
 Planning Time: 0.646 ms
 Execution Time: 183889.371 ms
(4 rows)


Das ist schon deutlich schneller.



Ich erstelle mal noch einen anderen Index, der die 2 Spalten als INCLUDE hat (PostgreSQL-Feature):

test=# create index idx_logger_include on logger(created) include (c180,c280);
CREATE INDEX

Nun frage ich mal die Daten nur für einen Tag ab:

test=# explain analyse select created, c180, c180 - lag(c180) over (order by created), c280, c280 - lag(c280) over (order by created) from logger where created between '2007-07-28'::date and '2007-07-29'::date;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------
 WindowAgg  (cost=0.43..86.96 rows=1377 width=40) (actual time=0.216..24.339 rows=1441 loops=1)    ->  Index Only Scan using idx_logger_include on logger (cost=0.43..55.97 rows=1377 width=24) (actual time=0.187..8.642 rows=1441 loops=1)          Index Cond: ((created >= '2007-07-28'::date) AND (created <= '2007-07-29'::date))
         Heap Fetches: 0
 Planning Time: 0.160 ms
 Execution Time: 31.503 ms
(6 rows)


Das dürfte für Dich realistischer sein. Nun lösche ich beide Indexe und führe letztere Abfrage noch mal aus:


test=# drop index idx_logger;
DROP INDEX
test=# drop index idx_logger_include
test-# ;
DROP INDEX
test=#
test=#
test=# explain analyse select created, c180, c180 - lag(c180) over (order by created), c280, c280 - lag(c280) over (order by created) from logger where created between '2007-07-28'::date and '2007-07-29'::date;
                                                               QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------
 WindowAgg  (cost=127220.61..127411.97 rows=1377 width=40) (actual time=336.972..356.114 rows=1441 loops=1)    ->  Gather Merge  (cost=127220.61..127380.98 rows=1377 width=24) (actual time=336.853..343.285 rows=1441 loops=1)
         Workers Planned: 2
         Workers Launched: 2
         ->  Sort  (cost=126220.58..126222.02 rows=574 width=24) (actual time=330.211..332.998 rows=480 loops=3)
               Sort Key: created
               Sort Method: quicksort  Memory: 25kB
               Worker 0:  Sort Method: quicksort  Memory: 25kB
               Worker 1:  Sort Method: quicksort  Memory: 161kB
               ->  Parallel Seq Scan on logger (cost=0.00..126194.28 rows=574 width=24) (actual time=261.502..328.286 rows=480 loops=3)                      Filter: ((created >= '2007-07-28'::date) AND (created <= '2007-07-29'::date))
                     Rows Removed by Filter: 3332853
 Planning Time: 0.106 ms
 Execution Time: 361.985 ms
(14 rows)


Du siehst, ohne Index ist die Abfrage Faktor 10 langsamer. Was hast Du an Mengen von Datensätzen, daß das bei Dir so lange dauert? (PostgreSQL führt die Abfrage auf 3 Cores aus, der Hauptprozess startet 2 Worker-Prozesse)


Btw: was mir grad auffällt, bei den gezeigten Lösungen bisher wird die Differenz der Zeitspalte berechnet, die aber eigentlich immer 2 Minuten sein sollte, nach Deinen Ausführungen. Ich habe die Differenz der Meßwerte zur vorherigen Messung berechnet, was ist richtig?


Andreas


--
2ndQuadrant, an EDB company
www.2ndQuadrant.com / www.enterprisedb.com


Antwort per Email an