PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Richtiger Weg, um auf die letzte Zeile für jede einzelne Kennung zuzugreifen?

Hier ist ein schneller Leistungsvergleich für die in diesem Beitrag erwähnten Abfragen.

Aktuelle Einrichtung:

Die Tabelle core_message hat 10.904.283 Zeilen und es gibt 60.740 Zeilen in test_boats (oder 60.740 verschiedene mmsi in core_message ).

Und ich verwende PostgreSQL 11.5

Abfrage mit Index-Only-Scan :

1) mit DISTINCT ON :

SELECT DISTINCT ON (mmsi) mmsi 
FROM core_message;

2) mit RECURSIVE mit LATERAL :

WITH RECURSIVE cte AS (
   (
   SELECT mmsi
   FROM   core_message
   ORDER  BY mmsi
   LIMIT  1
   )
   UNION ALL
   SELECT m.*
   FROM   cte c
   CROSS  JOIN LATERAL (
      SELECT mmsi
      FROM   core_message
      WHERE  mmsi > c.mmsi
      ORDER  BY mmsi
      LIMIT  1
      ) m
   )
TABLE cte;

3) Verwendung einer zusätzlichen Tabelle mit LATERAL :

SELECT a.mmsi
FROM test_boats a
CROSS JOIN LATERAL(
    SELECT b.time
    FROM core_message b
    WHERE a.mmsi = b.mmsi
    ORDER BY b.time DESC
    LIMIT 1
) b;

Abfrage ohne Nur-Index-Scan :

4) mit DISTINCT ON mit mmsi,time DESC INDEX :

SELECT DISTINCT ON (mmsi) * 
FROM core_message 
ORDER BY mmsi, time desc;

5) mit DISTINCT ON mit rückwärts mmsi,time UNIQUE CONSTRAINT :

SELECT DISTINCT ON (mmsi) * 
FROM core_message 
ORDER BY mmsi desc, time desc;

6) mit RECURSIVE mit LATERAL und mmsi,time DESC INDEX :

WITH RECURSIVE cte AS (
   (
   SELECT *
   FROM   core_message
   ORDER  BY mmsi , time DESC 
   LIMIT  1
   )
   UNION ALL
   SELECT m.*
   FROM   cte c
   CROSS  JOIN LATERAL (
      SELECT *
      FROM   core_message
      WHERE  mmsi > c.mmsi
      ORDER  BY mmsi , time DESC 
      LIMIT  1
      ) m
   )
TABLE cte;

7) mit RECURSIVE mit LATERAL und rückwärts mmsi,time UNIQUE CONSTRAINT :

WITH RECURSIVE cte AS (

   (

   SELECT *
   FROM   core_message
   ORDER  BY mmsi DESC , time DESC 
   LIMIT  1
   )
   UNION ALL
   SELECT m.*
   FROM   cte c
   CROSS  JOIN LATERAL (
      SELECT *
      FROM   core_message
      WHERE  mmsi < c.mmsi
      ORDER  BY mmsi DESC , time DESC 
      LIMIT  1
      ) m
   )
TABLE cte;

8) Verwendung einer zusätzlichen Tabelle mit LATERAL :

SELECT b.*
FROM test_boats a
CROSS JOIN LATERAL(
    SELECT b.*
    FROM core_message b
    WHERE a.mmsi = b.mmsi
    ORDER BY b.time DESC
    LIMIT 1
) b;

Verwendung einer eigenen Tabelle für die letzte Nachricht:

9) Hier ist meine anfängliche Lösung, die eine eigene Tabelle mit nur der letzten Nachricht verwendet. Diese Tabelle wird gefüllt, wenn neue Nachrichten eintreffen, könnte aber auch so erstellt werden:

CREATE TABLE core_shipinfos AS (
    WITH RECURSIVE cte AS (
       (
       SELECT *
       FROM   core_message
       ORDER  BY mmsi DESC , time DESC 
       LIMIT  1
       )
       UNION ALL
       SELECT m.*
       FROM   cte c
       CROSS  JOIN LATERAL (
          SELECT *
          FROM   core_message
          WHERE  mmsi < c.mmsi
          ORDER  BY mmsi DESC , time DESC 
          LIMIT  1
          ) m
       )
    TABLE cte);

Dann ist die Anforderung, die neueste Nachricht zu erhalten, so einfach:

SELECT * FROM core_shipinfos;

Ergebnisse:

Durchschnitt mehrerer Abfragen (ungefähr 5 für die schnelle):

1) 9146 ms
2) 728 ms
3) 498 ms

4) 51488 ms
5) 54764 ms
6) 729 ms
7) 778 ms
8) 516 ms

9) 15 ms

Fazit:

Ich werde die dedizierte Tabellenlösung nicht kommentieren und das bis zum Ende behalten.

Die zusätzliche Tabelle (test_boats )-Lösung ist hier definitiv der Gewinner, aber die RECURSIVE Lösung ist auch ziemlich effizient.

Für DISTINCT ON gibt es eine große Leistungslücke Verwenden von Index-Only-Scan und derjenige, der ihn nicht verwendet, ist der Leistungsgewinn für die andere effiziente Abfrage eher gering.

Dies ist sinnvoll, da die Hauptverbesserung, die diese Abfragen mit sich bringen, darin besteht, dass sie nicht die gesamte core_message durchlaufen müssen Tabelle, aber nur auf einer Teilmenge des eindeutigen mmsi das ist deutlich kleiner (60K+) im Vergleich zur core_message Tabellengröße (10M+)

Als zusätzliche Anmerkung scheint es keine signifikante Verbesserung der Leistung für die Abfragen zu geben, die die UNIQUE CONSTRAINT verwenden wenn ich mmsi,time DESC weglasse INDEX . Aber wenn ich diesen Index lösche, spare ich natürlich etwas Platz (dieser Index nimmt derzeit 328 MB ein)

Über die dedizierte Tischlösung:

Alle Nachrichten werden in core_message gespeichert Die Tabelle enthält sowohl Positionsinformationen (Position, Geschwindigkeit, Kurs usw.) als auch Schiffsinformationen (Name, Rufzeichen, Abmessungen usw.) sowie die Schiffskennung (mmsi).

Um etwas mehr Hintergrund zu geben, was ich eigentlich versuche:Ich implementiere ein Backend, um Nachrichten zu speichern, die von Schiffen über AIS-Protokoll .

Daher habe ich jede eindeutige mmsi, die ich erhalten habe, über dieses Protokoll erhalten. Es ist keine vordefinierte Liste. Es fügt ständig neue MMSI hinzu, bis ich alle Schiffe der Welt mit AIS habe.

In diesem Zusammenhang ist eine eigene Tabelle mit Schiffsinformationen als letzte erhaltene Nachricht sinnvoll.

Ich könnte die Verwendung einer solchen Tabelle vermeiden, wie wir es bei RECURSIVE gesehen haben Lösung, aber ... eine dedizierte Tabelle ist immer noch 50x schneller als dieses RECURSIVE Lösung.

Diese dedizierte Tabelle ähnelt in der Tat dem test_boat Tabelle, mit mehr Informationen als nur dem mmsi aufstellen. Wie es ist, eine Tabelle mit mmsi zu haben einziges Feld oder eine Tabelle mit den letzten Informationen der core_message Tabelle fügt meiner Anwendung die gleiche Komplexität hinzu.

Am Ende denke ich, dass ich mich für diesen speziellen Tisch entscheiden werde. Es wird mir eine unschlagbare Geschwindigkeit geben und ich werde immer noch die Möglichkeit haben, den LATERAL zu verwenden Trick auf core_message , was mir mehr Flexibilität gibt.