PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Właściwy sposób na dostęp do ostatniego wiersza dla każdego indywidualnego identyfikatora?

Oto szybkie porównanie wydajności dla zapytań wspomnianych w tym poście.

Obecna konfiguracja:

Tabela core_message ma 10 904 283 wierszy i jest 60 740 wierszy w test_boats (lub 60 740 odrębnych mmsi w core_message ).

A ja używam PostgreSQL 11.5

Zapytanie za pomocą skanowania tylko indeksu:

1) za pomocą DISTINCT ON :

SELECT DISTINCT ON (mmsi) mmsi 
FROM core_message;

2) za pomocą RECURSIVE z 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) Korzystanie z dodatkowej tabeli z 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;

Zapytanie nie używające skanowania tylko do indeksu:

4) za pomocą DISTINCT ON z mmsi,time DESC INDEX :

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

5) za pomocą DISTINCT ON z do tyłu mmsi,time UNIQUE CONSTRAINT :

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

6) za pomocą RECURSIVE z LATERAL i 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) za pomocą RECURSIVE z LATERAL i wstecz 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) Korzystanie z dodatkowej tabeli z 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;

Korzystanie z dedykowanej tabeli dla ostatniej wiadomości:

9) Oto moje początkowe rozwiązanie, wykorzystujące odrębną tabelę z tylko ostatnią wiadomością. Ta tabela jest wypełniana w miarę napływania nowych wiadomości, ale można ją również utworzyć w następujący sposób:

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

Wtedy prośba o otrzymanie najnowszej wiadomości jest tak prosta:

SELECT * FROM core_shipinfos;

Wyniki:

Średnia z wielu zapytań (około 5 w przypadku szybkiego):

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

Wniosek:

Nie będę komentował rozwiązania dotyczącego dedykowanego stołu i zachowam to na koniec.

Dodatkowa tabela (test_boats ) rozwiązanie jest tutaj zdecydowanie zwycięzcą, ale RECURSIVE rozwiązanie jest również dość wydajne.

Istnieje ogromna luka w wydajności dla DISTINCT ON przy użyciu skanowania tylko indeksu i tym, które go nie używa, ale wzrost wydajności jest raczej niewielki w przypadku drugiego wydajnego zapytania.

Ma to sens, ponieważ głównym ulepszeniem, jakie przynoszą te zapytania, jest fakt, że nie muszą one zapętlać całego core_message tabeli, ale tylko na podzbiorze unikalnych mmsi to jest znacznie mniejsze (60K+) w porównaniu do core_message rozmiar stołu (10M+)

Jako dodatkowa uwaga, wydaje się, że nie ma znaczącej poprawy wydajności dla zapytań używających UNIQUE CONSTRAINT jeśli upuszczę mmsi,time DESC INDEX . Ale upuszczenie tego indeksu oczywiście zaoszczędzi mi trochę miejsca (ten indeks zajmuje obecnie 328 MB)

O dedykowanym rozwiązaniu stołu:

Każda wiadomość przechowywana w core_message tabela zawiera zarówno informacje o pozycji (pozycja, prędkość, kurs itp.) ORAZ informacje o statku (nazwa, znak wywoławczy, wymiary itp.), a także identyfikator statku (mmsi).

Aby dać nieco więcej informacji na temat tego, co faktycznie próbuję zrobić:implementuję backend do przechowywania wiadomości emitowanych przez statki za pośrednictwem Protokół AIS .

W związku z tym każdy unikalny mmsi, który dostałem, otrzymywałem za pomocą tego protokołu. Nie jest to z góry zdefiniowana lista. Dodaje nowe MMSI, dopóki nie otrzymam wszystkich statków na świecie za pomocą AIS.

W tym kontekście dedykowana tabela z informacjami o statku jako ostatnią otrzymaną wiadomością ma sens.

Mógłbym uniknąć używania takiej tabeli, jak widzieliśmy z RECURSIVE rozwiązanie, ale... dedykowana tabela jest wciąż 50x szybsza niż ta RECURSIVE rozwiązanie.

Ta dedykowana tabela jest w rzeczywistości podobna do test_boat tabelę zawierającą więcej informacji niż tylko mmsi pole. Jak to jest, posiadanie tabeli z mmsi tylko pole lub tabela z wszystkimi ostatnimi informacjami core_message tabela dodaje tę samą złożoność do mojej aplikacji.

W końcu myślę, że pójdę na ten dedykowany stolik. Zapewni mi to bezkonkurencyjną prędkość i nadal będę miał możliwość korzystania z LATERAL sztuczka na core_message , co zapewni mi większą elastyczność.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Wdrażanie do heroku z projektem clojure, problemy ze środowiskiem produkcyjnym

  2. Czy mogę podzielić zapytanie na wiele zapytań lub utworzyć równoległość, aby przyspieszyć zapytanie?

  3. Dziesięć sposobów na rozszerzenie funkcjonalności PostgreSQL

  4. Nie można uruchomić Postgres.app na porcie 5432

  5. Jak INTERSECT działa w PostgreSQL