sql >> Database teknologi >  >> RDS >> PostgreSQL

Korrekt måde at få adgang til seneste række for hver enkelt identifikator?

Her er en hurtig ydelsessammenligning for de forespørgsler, der er nævnt i dette indlæg.

Nuværende opsætning:

Tabellen core_message har 10.904.283 rækker, og der er 60.740 rækker i test_boats (eller 60.740 forskellige mmsi i core_message ).

Og jeg bruger PostgreSQL 11.5

Forespørgsel ved hjælp af kun indeksscanning:

1) ved at bruge DISTINCT ON :

SELECT DISTINCT ON (mmsi) mmsi 
FROM core_message;
 

2) ved at bruge RECURSIVE med 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) Brug af en ekstra tabel med 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;
 

Forespørgsel bruger ikke kun indeksscanning :

4) ved at bruge DISTINCT ON med mmsi,time DESC INDEX :

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

5) ved at bruge DISTINCT ON med baglæns mmsi,time UNIQUE CONSTRAINT :

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

6) ved at bruge RECURSIVE med LATERAL og 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) ved at bruge RECURSIVE med LATERAL og baglæns 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) Brug af en ekstra tabel med 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;
 

Brug af en dedikeret tabel til den sidste besked:

9) Her er min første løsning ved at bruge en særskilt tabel med kun den sidste besked. Denne tabel udfyldes efterhånden som nye meddelelser ankommer, men kan også oprettes som sådan :

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);
 

Så er anmodningen om at få den seneste besked så simpel som den :

SELECT * FROM core_shipinfos;
 

Resultater:

Gennemsnit af flere forespørgsler (omkring 5 for den hurtige):

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

Konklusion:

Jeg vil ikke kommentere på den dedikerede bordløsning og vil beholde den til sidst.

Den ekstra tabel (test_boats ) løsningen er absolut vinderen her, men den RECURSIVE løsningen er også ret effektiv.

Der er et stort hul i ydeevnen for DISTINCT ON bruger kun indeksscanning, og den der ikke bruger det, men ydeevneforøgelsen er ret lille for den anden effektive forespørgsel.

Dette giver mening, da den største forbedring, disse forespørgsler medfører, er det faktum, at de ikke behøver at gå over hele core_message tabel, men kun på en delmængde af den unikke mmsi der er væsentligt mindre (60K+) sammenlignet med core_message bordstørrelse (10M+)

Som en yderligere bemærkning, synes der ikke at være væsentlig forbedring i ydeevnen for forespørgsler, der bruger UNIQUE CONSTRAINT hvis jeg dropper mmsi,time DESC INDEX . Men at droppe det indeks vil selvfølgelig spare mig noget plads (dette indeks tager i øjeblikket 328 MB)

Om den dedikerede bordløsning:

Hver meddelelse er gemt i core_message tabel indeholder både positionsoplysninger (position, hastighed, kurs osv.) OG skibsinformationer (navn, kaldesignal, dimensioner osv.), såvel som skibsidentifikator (mmsi).

For at give lidt mere baggrund om, hvad jeg faktisk forsøger at gøre:Jeg implementerer en backend til at gemme beskeder udsendt af skibe via AIS-protokol .

Som sådan, hver unik mmsi, jeg fik, fik jeg den via denne protokol. Det er ikke en foruddefineret liste. Det bliver ved med at tilføje nyt MMSI, indtil jeg fik alle skibe i verden ved hjælp af AIS.

I den sammenhæng giver en dedikeret tabel med skibsinformationer som sidst modtaget besked mening.

Jeg kunne undgå at bruge sådan en tabel, som vi har set med RECURSIVE løsning, men... en dedikeret tabel er stadig 50 gange hurtigere end denne RECURSIVE løsning.

Den dedikerede tabel ligner faktisk test_boat tabel med flere oplysninger end blot mmsi Mark. Som det er, at have en tabel med mmsi eneste felt eller en tabel med hver sidste information om core_message tabel tilføje den samme kompleksitet til min ansøgning.

I sidste ende tror jeg, at jeg vil gå efter dette dedikerede bord. Det vil give mig uovertruffen hastighed, og jeg vil stadig have mulighed for at bruge LATERAL trick på core_message , hvilket vil give mig mere fleksibilitet.



  1. Hvornår/Hvorfor tilføjer Oracle NaN til en række i en databasetabel

  2. SqlBulkCopy tilsvarende i MySql?

  3. MySQL Joins:at vælge hvilken tabel der skal tilsluttes fra baseret på kildetabeldata

  4. Ansible elsker PostgreSQL