我试图用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"
解释似乎很容易。你要加入两张桌子,Alerts
和SubscriptionFeed
。并且您希望查看具有最高日期的20个结果行。每个SubscriptionFeed
行都属于Alerts
行,但不是每个Alerts
行都必然与SubscriptionFeed
行相关。
所以,当你想要最新的qazxsw poi行时,这很容易:取最后20个qazxsw poi行(来自索引),加入他们的20个SubscriptionFeed
行,你就完成了。
当您想要最新的SubscriptionFeed
时,DBMS将采用最后一个Alerts
行,加入其所有订阅,检查它是否已经有二十行,如果没有,则取下一个Alerts
行,再次加入其所有订阅,检查是否有二十行到达,等等。好吧,DBMS可能会使用另一种算法,但它永远不会像最新的Alerts
那样简单。
而已。我们不太可能得到Alerts
query几乎与SubscriptionFeed
查询一样快。但是我们可以考虑如何帮助DBMS访问行:Alerts
上的现有索引可以帮助DBMS快速找到最新的SubscriptionFeed
行。为了快速获得他们相关的Alerts(createdAt DESC, id DESC)
,你需要一个关于Alerts
的索引。 (好吧,也许你已经有了,因为SubscriptionFeed
引用了SubscriptionFeed(alertId)
。)
除此之外,您还可以提供覆盖索引,其中包含您在查询中使用的表中的所有列(即将其他列添加到已提到的索引中),例如:
SubscriptionFeed.alertId
这回答了问题的原始版本。
Postgres对索引中键的排序非常挑剔。我建议将查询编写为:
Alerts.id
然后包括以下索引:
create index idx on SubscriptionFeed(alertId, name);
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;
。我在其他答案中解释了这个问题。以下是关于如何加速查询的想法。
您的查询会通过订阅获取最新警报。你减少了20个结果行,因此可能最终得到一些随机选择的行(例如,如果两个最新的警报各有15个订阅,你将选择最新警报的所有订阅,并为另一个警报选择五个随机的订阅)。
我们不知道结果中会有多少不同的警报。但我们知道它永远不会超过20.所以,这是你可以尝试的东西:
SubscriptionFeed(alertId)
此查询的作用是:首先选择最新的20个警报。然后内部加入子标记。因此,我们最终得到至少20行,但它可能是100,1000或100万,具体取决于每个警报的订阅数量。 (我认为很可能是每个警报都有很多可疑的,所以不应该有很多行加入。)最后我们再次限制结果,最终不超过20。
索引:
(这个查询实际上不应该对你自己的查询产生影响,因为很明显结果中不会有超过20个警报。但也许这有助于优化器看到这一点。我想这值得一试。)