我有一张 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) 使用
RECURSIVE
与 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) 使用带有
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 ON
与 mmsi,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) 使用
RECURSIVE
与 LATERAL
和 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) 使用
RECURSIVE
与 LATERAL
和落后 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/