sql >> Base de Datos >  >> RDS >> PostgreSQL

¿La forma correcta de acceder a la última fila para cada identificador individual?

Aquí hay una comparación rápida de rendimiento para las consultas mencionadas en esta publicación.

Configuración actual:

La tabla core_message tiene 10,904,283 filas y hay 60,740 filas en test_boats (o 60.740 mmsi distintos en core_message ).

Y estoy usando PostgreSQL 11.5

Consulta mediante análisis de solo índice:

1) usando DISTINCT ON :

SELECT DISTINCT ON (mmsi) mmsi 
FROM core_message;

2) usando RECURSIVE con 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) Usando una tabla extra con 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;

Consulta que no usa escaneo de solo índice:

4) usando DISTINCT ON con mmsi,time DESC INDEX :

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

5) usando DISTINCT ON con hacia atrás mmsi,time UNIQUE CONSTRAINT :

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

6) usando RECURSIVE con LATERAL y 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) usando RECURSIVE con LATERAL y hacia atrás 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) Usando una tabla extra con 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;

Usando una tabla dedicada para el último mensaje:

9) Aquí está mi solución inicial, usando una tabla distinta con solo el último mensaje. Esta tabla se completa a medida que llegan nuevos mensajes, pero también podría crearse así:

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

Entonces la solicitud para recibir el último mensaje es tan simple como eso:

SELECT * FROM core_shipinfos;

Resultados:

Promedio de consulta múltiple (alrededor de 5 para la rápida):

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) 15ms

Conclusión:

No comentaré sobre la solución de mesa dedicada y la dejaré para el final.

La tabla adicional (test_boats ) la solución es definitivamente la ganadora aquí, pero el RECURSIVE La solución también es bastante eficiente.

Hay una gran diferencia en el rendimiento de DISTINCT ON usando escaneo de solo índice y el que no lo usa, pero la ganancia de rendimiento es bastante pequeña para la otra consulta eficiente.

Esto tiene sentido ya que la principal mejora que aportan esas consultas es el hecho de que no necesitan recorrer todo el core_message tabla, pero solo en un subconjunto de los únicos mmsi eso es significativamente más pequeño (60K+) en comparación con el core_message tamaño de la mesa (10M+)

Como nota adicional, no parece haber una mejora significativa en el rendimiento de las consultas que utilizan la UNIQUE CONSTRAINT si dejo caer el mmsi,time DESC INDEX . Pero dejar ese índice, por supuesto, me ahorrará algo de espacio (este índice actualmente ocupa 328 MB)

Acerca de la solución de mesa dedicada:

Cada mensaje almacenado en el core_message La tabla contiene información de posición (posición, velocidad, rumbo, etc.) E información del barco (nombre, indicativo, dimensiones, etc.), así como el identificador del barco (mmsi).

Para brindar un poco más de información sobre lo que realmente estoy tratando de hacer:estoy implementando un backend para almacenar mensajes emitidos por barcos a través de Protocolo AIS .

Como tal, cada mmsi único que obtuve, lo obtuve a través de este protocolo. No es una lista predefinida. Sigue agregando nuevos MMSI hasta que tengo todos los barcos del mundo que usan AIS.

En ese contexto, tiene sentido una tabla dedicada con información del barco como el último mensaje recibido.

Podría evitar usar una tabla como la que hemos visto con RECURSIVE solución, pero... una tabla dedicada sigue siendo 50 veces más rápida que esta RECURSIVE solución.

Esa tabla dedicada es, de hecho, similar al test_boat tabla, con más información que solo el mmsi campo. Tal como está, tener una tabla con mmsi único campo o una tabla con toda la información del core_message table agrega la misma complejidad a mi aplicación.

Al final, creo que optaré por esta mesa dedicada. Me dará una velocidad imbatible y aún tendré la posibilidad de usar el LATERAL truco en core_message , lo que me dará más flexibilidad.