sql - 访问每个标识符的最新行的正确方法?

标签 sql postgresql indexing query-optimization greatest-n-per-group

我有一张 table core_message在 Postgres 中,使用 百万 看起来像这样的行(简化):

┌────────────────┬──────────────────────────┬─────────────────┬───────────┬──────────────────────────────────────────┐
│    Colonne     │           Type           │ Collationnement │ NULL-able │                Par défaut                │
├────────────────┼──────────────────────────┼─────────────────┼───────────┼──────────────────────────────────────────┤
│ id             │ integer                  │                 │ not null  │ nextval('core_message_id_seq'::regclass) │
│ mmsi           │ integer                  │                 │ not null  │                                          │
│ time           │ timestamp with time zone │                 │ not null  │                                          │
│ point          │ geography(Point,4326)    │                 │           │                                          │
└────────────────┴──────────────────────────┴─────────────────┴───────────┴──────────────────────────────────────────┘
Index:
    "core_message_pkey" PRIMARY KEY, btree (id)
    "core_message_uniq_mmsi_time" UNIQUE CONSTRAINT, btree (mmsi, "time")
    "core_messag_mmsi_b36d69_idx" btree (mmsi, "time" DESC)
    "core_message_point_id" gist (point)
mmsi列是用于识别世界上船只的唯一标识符。我正在尝试获取每个 mmsi 的最新行.

我可以这样得到,例如:
SELECT a.* FROM core_message a
JOIN  (SELECT mmsi, max(time) AS time FROM core_message GROUP BY mmsi) b
       ON a.mmsi=b.mmsi and a.time=b.time;

但这太慢了,2秒+。

所以我的解决方案是创建一个仅包含 core_message 的最新行( 100K+ 最大行 )的不同表表,称为 LatestMessage .

每次必须将新行添加到 core_message 时,都会通过我的应用程序填充此表。 .

它工作得很好,我能够在几毫秒内访问该表。
但是我很想知道是否有更好的方法可以仅使用一个表来实现这一目标并保持相同级别的数据访问性能。

最佳答案

这是本文中提到的查询的快速性能比较。

当前设置:

core_message有 10,904,283 行,test_boats 中有 60,740 行(或 core_message 中的 60,740 个不同的 mmsi)。

我正在使用 PostgreSQL 11.5

使用仅索引扫描查询:

1) 使用 DISTINCT ON :

SELECT DISTINCT ON (mmsi) mmsi 
FROM core_message;

2) 使用 RECURSIVELATERAL :
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) 使用带有 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;

查询不使用仅索引扫描:

4) 使用DISTINCT ONmmsi,time DESC INDEX :
SELECT DISTINCT ON (mmsi) * 
FROM core_message 
ORDER BY mmsi, time desc;

5) 使用 DISTINCT ON与落后 mmsi,time UNIQUE CONSTRAINT :
SELECT DISTINCT ON (mmsi) * 
FROM core_message 
ORDER BY mmsi desc, time desc;

6) 使用 RECURSIVELATERALmmsi,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) 使用 RECURSIVELATERAL和落后 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) 使用带有 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;

为最后一条消息使用专用表:

9)这是我最初的解决方案,使用只有最后一条消息的不同表。此表在新消息到达时填充,但也可以像这样创建:
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);

那么获取最新消息的请求就这么简单:
SELECT * FROM core_shipinfos;

结果:

多个查询的平均值(快速查询约为 5):

1) 9146 毫秒
2) 728 毫秒
3) 498 毫秒

4) 51488 毫秒
5) 54764 毫秒
6) 729 毫秒
7) 778 毫秒
8) 516 毫秒

9) 15 毫秒

结论:

我不会评论专用表解决方案,并将保留到最后。

附加表( test_boats )解决方案绝对是这里的赢家,但 RECURSIVE解决方案也非常有效。
DISTINCT ON 的性能差距很大使用仅索引扫描和不使用它的扫描,但是对于其他有效查询而言,性能增益相当小。

这是有道理的,因为这些查询带来的主要改进是它们不需要循环整个 core_message。表,但仅在唯一 mmsi 的子集上与 core_message 相比要小得多(60K+)。表大小(10M+)

作为附加说明,使用 UNIQUE CONSTRAINT 的查询的性能似乎没有显着提高。如果我放弃 mmsi,time DESC INDEX .但是删除该索引当然会为我节省一些空间(该索引目前占用 328MB)

关于专 table 解决方案:

每条消息存储在core_message表包含位置信息(位置、速度、航向等)和船舶信息(名称、呼号、尺寸等)以及船舶标识符 (mmsi)。

为我实际尝试做的事情提供更多背景知识:我正在实现一个后端来存储船舶通过 AIS protocol 发出的消息。 .

因此,我得到的每一个独特的 mmsi,都是通过这个协议(protocol)得到的。它不是一个预定义的列表。它不断添加新的 MMSI,直到我让世界上的每艘船都使用 AIS。

在这种情况下,将船舶信息作为最后收到的消息的专用表是有意义的。

我可以避免使用我们在 RECURSIVE 中看到的这种表格。解决方案,但是...专用表仍然比 RECURSIVE 快 50 倍解决方案。

该专用表实际上类似于 test_boat表,提供的信息不仅仅是 mmsi field 。事实上,拥有一张带有 mmsi 的表格仅包含 core_message 的每个最后信息的字段或表格表给我的应用程序增加了同样的复杂性。

最后,我想我会去这张专用 table 。它会给我无与伦比的速度,我仍然可以使用LATERAL欺骗core_message ,这将给我更多的灵 active 。

关于sql - 访问每个标识符的最新行的正确方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57893251/

相关文章:

sql - 左连接返回重复行

java - 使用参数化 IN 子句时 N1QL 查询超时

python - 使用 if 语句过滤数据?

java - 尝试运行sql语句时当前光标位置的操作无效

sql - 将数据从 MS Sql Server 存储过程导出到 Excel 文件

java - OpenJPA 左外连接条件

postgresql - 地理服务器错误 : function postgis_lib_version()

regex - 不要提取错误的子串/数字

sql - 不使用 Max()/Greatest() 函数查找最大组值

Python 数据框 : Finding a value in same row as a defined value in a different column