Ich finde es schwierig zu verstehen, warum in dieser Abfrage eine Menge Heap-Abrufe stattfinden. Nach meinem Verständnis sollte die umgekehrte Suche des Index genauso schnell sein wie die direkte Suche und umgekehrt, wenn der Index keine Nullen (an beiden Enden) enthält.
Ich vermute, dass der Vorwärts- / Rückwärtsscan tatsächlich ein roter Hering ist, aber ich kann keinen anderen bedeutenden Unterschied in dieser Erklärungsausgabe erkennen.
Hier ist das Tabellenlayout. Ich habe die ersten beiden Spalten anonymisiert, von denen ich glaube, dass sie für das Problem nicht relevant sind, aber ich habe sie und ihre Indizes der Vollständigkeit halber beibehalten.
testqueuedb=> \d+ queue
Table "public.queue"
Column | Type | Modifiers | Storage | Stats target | Description
-----------------------+--------------------------+-------------------------------------------------------------+----------+--------------+-------------
foo | character varying(64) | not null | extended | |
bar | numeric(6,0) | not null | main | |
worker | character varying(32) | not null | extended | |
queued | timestamp with time zone | not null default (timeofday())::timestamp without time zone | plain | |
Indexes:
"queue_idx_job" btree (foo, bar, worker)
"queue_idx_worker" btree (worker, queued)
Foreign-key constraints:
"queue_fk_worker" FOREIGN KEY (worker) REFERENCES workers(worker)
Und hier sind die verschiedenen Min / Max-Erklärungen.
testqueuedb=> explain (analyze, buffers) select min(queued) from queue where worker = 'workername';
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------
Result (cost=0.59..0.60 rows=1 width=0) (actual time=1019.490..1019.490 rows=1 loops=1)
Buffers: shared hit=20194 read=1
InitPlan 1 (returns $0)
-> Limit (cost=0.42..0.59 rows=1 width=8) (actual time=1019.485..1019.486 rows=1 loops=1)
Buffers: shared hit=20194 read=1
-> Index Only Scan using queue_idx_worker on queue (cost=0.42..55480.93 rows=330371 width=8) (actual time=1019.483..1019.483 rows=1 loops=1)
Index Cond: ((worker = 'workername'::text) AND (queued IS NOT NULL))
Heap Fetches: 20224
Buffers: shared hit=20194 read=1
Planning time: 0.197 ms
Execution time: 1019.529 ms
(11 rows)
testqueuedb=> explain (analyze, buffers) select max(queued) from queue where worker = 'workername';
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------
Result (cost=0.59..0.60 rows=1 width=0) (actual time=0.508..0.509 rows=1 loops=1)
Buffers: shared hit=2 read=3
InitPlan 1 (returns $0)
-> Limit (cost=0.42..0.59 rows=1 width=8) (actual time=0.503..0.503 rows=1 loops=1)
Buffers: shared hit=2 read=3
-> Index Only Scan Backward using queue_idx_worker on queue (cost=0.42..55480.93 rows=330371 width=8) (actual time=0.502..0.502 rows=1 loops=1)
Index Cond: ((worker = 'workername'::text) AND (queued IS NOT NULL))
Heap Fetches: 1
Buffers: shared hit=2 read=3
Planning time: 0.215 ms
Execution time: 0.546 ms
(11 rows)
Ich finde die Haufenabrufe in diesem ersten Beispiel besonders verwirrend. Kommt es auf die Pufferung an?
Die Postgres-Version ist 9.5.5.
Es gibt ungefähr 500.000 Zeilen für jeden Mitarbeiter in der Tabelle und nur sehr wenige unterschiedliche Mitarbeiter - weniger als zehn -, was mich glauben lässt, dass der Index zunächst nicht richtig strukturiert ist, aber ich bin an dem Unterschied in diesen Abfragen interessiert ungeachtet.
quelle
select queued from queue where worker = 'workername' order by queued limit 1;
das gleiche Verhalten / erklärt?desc
in dieser Abfrage fast augenblicklich umgedreht.Antworten:
Ich bin mir ziemlich sicher, dass dies auf die Wahl von timestamptz als Datentyp für die
queued
Spalte zurückzuführen ist. Postgres muss alle Spalten besuchen, um sicherzustellen, dass aus Zeitzonengründen das wahre Maximum gefunden wurde. Aus diesem Grund zeigt der Index-Scan eine so hohe Anzahl.Sie sollten den
queued
Datentyp in einen int (oder bigint) ändern und ihn automatisch mit einer Sequenz inkrementieren. (Sie können natürlich die Zeitstempelspalte belassen, wenn Sie den Wert benötigen).quelle