min () / max () für mehrspaltigen Zeitstempelindex

7

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.

user115675
quelle
1
Zeigt select queued from queue where worker = 'workername' order by queued limit 1;das gleiche Verhalten / erklärt?
Ypercubeᵀᴹ
@ ypercubeᵀᴹ Ja, das tut es. In ähnlicher Weise wird die Reihenfolge descin dieser Abfrage fast augenblicklich umgedreht.
Benutzer115675
1
@ user115675 Ich habe gerade versucht, Ihr Problem auf Postgres 9.5 zu reproduzieren - kein Erfolg. Meine Warteschlangentabelle hatte dieselbe Verteilung wie Sie beschrieben. Ungefähr 400 MB Tabelle, ungefähr 150 MB Index. Die Pläne sind die gleichen, aber die Puffer-Couts sind viel kleiner und es gibt keine Anomalie beim Abrufen von Heaps. Bitte geben Sie weitere Informationen zum Datensatz an (genaue Größe, durchschnittliche Länge usw.)
Filiprem
2
Es ist eine gute Praxis, jede Erklärung zweimal auszuführen, um die Auswirkungen des Caching zu sehen. Macht das einen Unterschied?
Franc Drobnič

Antworten:

1

Ich bin mir ziemlich sicher, dass dies auf die Wahl von timestamptz als Datentyp für die queuedSpalte 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 queuedDatentyp 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).

dland
quelle
Ich denke nicht, dass timestamptz nur ein Zeitstempel in Zone UTC ist. Es sind keine Konvertierungen erforderlich, um sie zu vergleichen. (auf der Festplatte ist es eine große Anzahl von Mikrosekunden)
Jasen