我正在尝试使用ORDER BY
子句连接两个简单的表
表格:
警报:
690000
(createdAt DESC, id DESC)
SubscriptionFeed :
99990
(createdAt DESC)
问题是当我添加ORDER BY a."createdAt" DESC, a.id DESC
时,查询变得比使用ORDER BY sf."createdAt" DESC
时慢得多
我需要的查询及其解释计划
查询:
SELECT a.id, a."createdAt", sf."name"
FROM "Alerts" as a
INNER JOIN "SubscriptionFeed" as sf
ON a.id = sf."alertId"
ORDER BY a."createdAt" DESC, a.id DESC
LIMIT 20
解释平原:
"Limit (cost=0.84..81.54 rows=20 width=24) (actual time=7.926..5079.614 rows=20 loops=1)"
" -> Nested Loop (cost=0.84..403440.05 rows=99990 width=24) (actual time=7.923..5079.604 rows=20 loops=1)"
" -> Index Only Scan using idx_created_at_uuid on "Alerts" a (cost=0.42..69639.05 rows=690000 width=24) (actual time=5.897..3697.758 rows=630013 loops=1)"
" Heap Fetches: 630013"
" -> Index Only Scan using "SubscriptionFeed_alertId_subscriptionId_key" on "SubscriptionFeed" sf (cost=0.42..0.46 rows=2 width=16) (actual time=0.002..0.002 rows=0 loops=630013)"
" Index Cond: ("alertId" = a.id)"
" Heap Fetches: 20"
"Planning Time: 30.234 ms"
"Execution Time: 5079.773 ms"
带有ORDER BY sf."createdAt" DESC
的查询及其说明计划
查询:
SELECT a.id, a."createdAt", sf."name"
FROM "Alerts" as a
INNER JOIN "SubscriptionFeed" as sf
ON a.id = sf."alertId"
ORDER BY sf."createdAt" DESC
LIMIT 20
说明计划:
"Limit (cost=0.84..28.91 rows=20 width=32) (actual time=1.785..2.708 rows=20 loops=1)"
" -> Nested Loop (cost=0.84..140328.41 rows=99990 width=32) (actual time=1.784..2.703 rows=20 loops=1)"
" -> Index Only Scan using idx_subscription_feed_alert_id on "SubscriptionFeed" sf (cost=0.42..6582.83 rows=99990 width=24) (actual time=1.705..2.285 rows=20 loops=1)"
" Heap Fetches: 20"
" -> Index Scan using "Alerts_pkey" on "Alerts" a (cost=0.42..1.34 rows=1 width=24) (actual time=0.019..0.019 rows=1 loops=20)"
" Index Cond: (id = sf."alertId")"
"Planning Time: 3.758 ms"
"Execution Time: 2.865 ms"
答案 0 :(得分:1)
这回答了问题的原始版本。
Postgres对索引中键的顺序很挑剔。我建议将查询写为:
SELECT a.id, a."createdAt"
FROM "Alerts" a
WHERE EXISTS (SELECT 1
FROM "SubscriptionFeed" as sf
WHERE a.id = sf."alertId"
)
ORDER BY a."createdAt" DESC, a.id DESC
LIMIT 20;
然后包括以下索引:
SubscriptionFeed(alertId)
Alerts(createdAt desc, id desc)
。答案 1 :(得分:1)
解释似乎很容易。您正在联接两个表Alerts
和SubscriptionFeed
。您想查看具有最高日期的二十个结果行。每个SubscriptionFeed
行都属于一个Alerts
行,但并非每个Alerts
行都必须具有相关的SubscriptionFeed
行。
因此,当您想要最新的SubscriptionFeed
行时,这很容易:取最后20 SubscriptionFeed
行(来自索引),加入其20 Alerts
行,就可以了
当您要使用最新的Alerts
时,DBMS将使用最后的Alerts
行,加入其所有订阅,检查它是否已经有20行,如果没有,则进行下一个{{ 1}}行,再次加入其所有订阅,检查是否达到二十行,依此类推。嗯,DBMS可以使用另一种算法,但是它永远不会像最新的Alerts
那样简单。
就是这样。我们不太可能获得与SubscriptionFeed
查询几乎一样快的Alerts
查询。但是我们可以考虑如何帮助DBMS访问行:SubscriptionFeed
上的现有索引可以帮助DBMS快速找到最新的Alerts(createdAt DESC, id DESC)
行。为了快速获取其相关的Alerts
,您需要在SubscriptionFeed
上建立索引。 (好吧,考虑到SubscriptionFeed(alertId)
引用了SubscriptionFeed.alertId
,也许已经有了。)
除此之外,您还可以提供覆盖索引,其中包含您在查询中使用的表中的所有列(即,将其他列添加到已经提到的索引中),例如:
Alerts.id
答案 2 :(得分:1)
我已经在另一个答案中解释了这个问题。这是有关如何加快查询速度的想法。
您的查询获得有关其订阅的最新警报。您削减了20个结果行,因此最终可能会得到一些随机选择的行(例如,如果两个最新警报各有15个订阅,则将所有订阅都选择为最新警报,并从五个随机项中选择另一个警报)。 / p>
我们不知道结果中会有多少种不同的警报。但是我们知道它永远不会超过20。因此,您可以尝试以下方法:
select a.id, a.createdat, sf.name
from (select * from alerts order by a.createdat desc, a.id desc limit 20) as a
inner join subscriptionfeed as sf on sf.alertid = a.id
order by a.createdat desc, a.id desc
limit 20;
此查询的作用是:首先选择最新的20条警报。然后将内部加入订阅。因此,我们最终至少有20行,但可能是100、1000或100万行,具体取决于每个警报有多少个订阅。 (不过,我认为每个警报可能有 个订阅,所以不应有那么多行要加入。)最后,我们再次限制结果以便结束不超过二十个。
索引:
(此查询实际上不应与您自己的查询有所不同,因为很明显结果中的警报不能超过20个。但是也许它可以帮助优化器看到这一点。我想值得尝试一下。 )