sql >> Databáze >  >> RDS >> PostgreSQL

Správný způsob přístupu k poslednímu řádku pro každý jednotlivý identifikátor?

Zde je rychlé srovnání výkonu pro dotazy uvedené v tomto příspěvku.

Aktuální nastavení:

Tabulka core_message má 10 904 283 řádků a v test_boats je 60 740 řádků (nebo 60 740 odlišných mmsi v core_message ).

A já používám PostgreSQL 11.5

Dotaz pomocí skenování pouze indexu:

1) pomocí DISTINCT ON :

SELECT DISTINCT ON (mmsi) mmsi 
FROM core_message;

2) pomocí RECURSIVE s 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) Použití další tabulky s 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;

Dotaz nepoužívá skenování pouze na základě indexu:

4) pomocí DISTINCT ON s mmsi,time DESC INDEX :

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

5) pomocí DISTINCT ON se zpětným mmsi,time UNIQUE CONSTRAINT :

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

6) pomocí RECURSIVE s LATERAL a 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) pomocí RECURSIVE s LATERAL a zpětně 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) Použití další tabulky s 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;

Použití vyhrazené tabulky pro poslední zprávu:

9) Zde je moje počáteční řešení s použitím odlišné tabulky pouze s poslední zprávou. Tato tabulka se vyplňuje při příchodu nových zpráv, ale lze ji vytvořit také takto:

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

Pak je požadavek na získání nejnovější zprávy tak jednoduchý:

SELECT * FROM core_shipinfos;

Výsledky:

Průměr více dotazů (asi 5 pro rychlý):

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

Závěr:

Řešení vyhrazené tabulky nebudu komentovat a nechám si to na konec.

Dodatečná tabulka (test_boats ) řešení je zde rozhodně vítězem, ale RECURSIVE řešení je také docela efektivní.

U DISTINCT ON existuje obrovská mezera ve výkonu používající pouze indexové skenování a ten, který jej nepoužívá, ale nárůst výkonu je u druhého efektivního dotazu poměrně malý.

To dává smysl, protože hlavním zlepšením, které tyto dotazy přinášejí, je skutečnost, že nemusí opakovat celou core_message tabulce, ale pouze na podmnožině jedinečné mmsi která je výrazně menší (60 000+) ve srovnání s core_message velikost stolu (10M+)

Jako další poznámka se nezdá, že by došlo k výraznému zlepšení výkonu pro dotazy pomocí UNIQUE CONSTRAINT pokud vypustím mmsi,time DESC INDEX . Ale zrušením tohoto indexu mi samozřejmě ušetří místo (tento index v současné době zabírá 328 MB)

O řešení vyhrazené tabulky:

Každá zpráva uložená v core_message tabulka obsahuje jak poziční informace (poloha, rychlost, kurz atd.) A informace o lodi (jméno, volací znak, rozměry atd.), tak i identifikátor lodi (mmsi).

Abych uvedl trochu více pozadí o tom, co se vlastně snažím dělat:implementuji backend pro ukládání zpráv vysílaných loděmi přes protokol AIS .

Každý jedinečný mmsi, který jsem dostal, jsem dostal prostřednictvím tohoto protokolu. Nejedná se o předem definovaný seznam. Neustále přidává nové MMSI, dokud nezískám všechny lodě na světě pomocí AIS.

V tomto kontextu dává smysl vyhrazená tabulka s informacemi o lodi jako poslední přijatá zpráva.

Mohl bych se vyhnout použití takové tabulky, jak jsme viděli u RECURSIVE řešení, ale... vyhrazená tabulka je stále 50x rychlejší než tato RECURSIVE řešení.

Tato vyhrazená tabulka je ve skutečnosti podobná test_boat tabulka s více informacemi než jen mmsi pole. Jak to je, mít tabulku s mmsi pouze pole nebo tabulka s posledními informacemi core_message tabulka přidává mé aplikaci stejnou složitost.

Nakonec si myslím, že půjdu pro tento vyhrazený stůl. Poskytne mi to nepřekonatelnou rychlost a stále budu mít možnost používat LATERAL trik na core_message , což mi poskytne větší flexibilitu.



  1. Jak ignorovat parametr v uložené proceduře, pokud je jeho hodnota null

  2. použijte proměnnou \set uvnitř deklaračního bloku plpgsql

  3. Jaký je elegantní způsob, jak vrátit čitelnou „velikost souboru“ souboru uloženého ve sloupci Oracle blob pomocí SQL?

  4. Jak poznáte, že se s GroovyStrings nezachází stejně jako s řetězci?