sql >> Databasteknik >  >> RDS >> PostgreSQL

Rätt sätt att komma åt senaste raden för varje enskild identifierare?

Här är en snabb prestandajämförelse för de frågor som nämns i det här inlägget.

Nuvarande inställningar:

Tabellen core_message har 10 904 283 rader och det finns 60 740 rader i test_boats (eller 60 740 distinkta mmsi i core_message ).

Och jag använder PostgreSQL 11.5

Fråga med enbart index-skanning:

1) med DISTINCT ON :

SELECT DISTINCT ON (mmsi) mmsi 
FROM core_message;

2) med 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) Använda en extra tabell 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;

Frågan använder inte enbart indexsökning:

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

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

5) med DISTINCT ON med bakåt mmsi,time UNIQUE CONSTRAINT :

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

6) med RECURSIVE med LATERAL och 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) med RECURSIVE med LATERAL och bakåt 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) Använda en extra tabell 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;

Använda en dedikerad tabell för det senaste meddelandet:

9) Här är min första lösning, med hjälp av en distinkt tabell med endast det sista meddelandet. Den här tabellen fylls i när nya meddelanden kommer men kan också skapas så här :

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

Då är begäran om att få det senaste meddelandet så enkelt:

SELECT * FROM core_shipinfos;

Resultat :

Genomsnitt av flera frågor (cirka 5 för den snabba):

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

Slutsats:

Jag kommer inte att kommentera den dedikerade tabelllösningen och kommer att behålla den till slutet.

Tilläggstabellen (test_boats ) lösningen är definitivt vinnaren här men RECURSIVE lösningen är också ganska effektiv.

Det finns ett stort gap i prestanda för DISTINCT ON använder endast index-skanning och den som inte använder den, men prestandavinsten är ganska liten för den andra effektiva frågan.

Detta är vettigt eftersom den största förbättringen dessa frågor ger är det faktum att de inte behöver gå över hela core_message tabell men bara på en delmängd av den unika mmsi som är betydligt mindre (60K+) jämfört med core_message bordsstorlek (10M+)

Som en ytterligare notering verkar det inte vara någon betydande förbättring av prestanda för frågor som använder UNIQUE CONSTRAINT om jag släpper mmsi,time DESC INDEX . Men att släppa det indexet kommer naturligtvis att spara lite utrymme (det här indexet tar för närvarande 328 MB)

Om den dedikerade bordslösningen:

Varje meddelande lagras i core_message Tabellen innehåller både positionsinformation (position, hastighet, kurs, etc.) OCH fartygsinformation (namn, anropssignal, dimensioner, etc.), såväl som fartygsidentifierare (mmsi).

För att ge lite mer bakgrund om vad jag faktiskt försöker göra:Jag implementerar en backend för att lagra meddelanden som sänds ut av fartyg via AIS-protokoll .

Som sådan, varje unik mmsi jag fick, fick jag det via detta protokoll. Det är inte en fördefinierad lista. Det fortsätter att lägga till nytt MMSI tills jag fick alla fartyg i världen med AIS.

I det sammanhanget är en dedikerad tabell med fartygsinformation som det senaste mottagna meddelandet vettigt.

Jag skulle kunna undvika att använda en sådan tabell som vi har sett med RECURSIVE lösning, men... en dedikerad tabell är fortfarande 50 gånger snabbare än denna RECURSIVE lösning.

Den dedikerade tabellen liknar faktiskt test_boat tabell, med mer information än bara mmsi fält. Som det är, att ha en tabell med mmsi enda fält eller en tabell med alla sista uppgifterna i core_message tabell lägga till samma komplexitet till min ansökan.

I slutändan tror jag att jag kommer att gå för detta dedikerade bord. Det kommer att ge mig oslagbar hastighet och jag kommer fortfarande att ha möjligheten att använda LATERAL knep på core_message , vilket ger mig mer flexibilitet.



  1. Få rang av en rad i mysql-frågan

  2. Kan inte hitta min EF-kod första databas

  3. Kopiera tabell från en Oracle DB till en annan via enkelriktad DBLink

  4. SQL-fråga för att hitta primärnyckeln för en tabell?