进行内部连接时,索引顺序是慢的

问题描述 投票:1回答:3

我试图用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"
sql postgresql performance
3个回答
1
投票

解释似乎很容易。你要加入两张桌子,AlertsSubscriptionFeed。并且您希望查看具有最高日期的20个结果行。每个SubscriptionFeed行都属于Alerts行,但不是每个Alerts行都必然与SubscriptionFeed行相关。

所以,当你想要最新的qazxsw poi行时,这很容易:取最后20个qazxsw poi行(来自索引),加入他们的20个SubscriptionFeed行,你就完成了。

当您想要最新的SubscriptionFeed时,DBMS将采用最后一个Alerts行,加入其所有订阅,检查它是否已经有二十行,如果没有,则取下一个Alerts行,再次加入其所有订阅,检查是否有二十行到达,等等。好吧,DBMS可能会使用另一种算法,但它永远不会像最新的Alerts那样简单。

而已。我们不太可能得到Alertsquery几乎与SubscriptionFeed查询一样快。但是我们可以考虑如何帮助DBMS访问行:Alerts上的现有索引可以帮助DBMS快速找到最新的SubscriptionFeed行。为了快速获得他们相关的Alerts(createdAt DESC, id DESC),你需要一个关于Alerts的索引。 (好吧,也许你已经有了,因为SubscriptionFeed引用了SubscriptionFeed(alertId)。)

除此之外,您还可以提供覆盖索引,其中包含您在查询中使用的表中的所有列(即将其他列添加到已提到的索引中),例如:

SubscriptionFeed.alertId

1
投票

这回答了问题的原始版本。

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;

1
投票

我在其他答案中解释了这个问题。以下是关于如何加速查询的想法。

您的查询会通过订阅获取最新警报。你减少了20个结果行,因此可能最终得到一些随机选择的行(例如,如果两个最新的警报各有15个订阅,你将选择最新警报的所有订阅,并为另一个警报选择五个随机的订阅)。

我们不知道结果中会有多少不同的警报。但我们知道它永远不会超过20.所以,这是你可以尝试的东西:

SubscriptionFeed(alertId)

此查询的作用是:首先选择最新的20个警报。然后内部加入子标记。因此,我们最终得到至少20行,但它可能是100,1000或100万,具体取决于每个警报的订阅数量。 (我认为很可能是每个警报都有很多可疑的,所以不应该有很多行加入。)最后我们再次限制结果,最终不超过20。

索引:

  • 警报(createdat desc,id desc)
  • subscriptionfeed(alertid)

(这个查询实际上不应该对你自己的查询产生影响,因为很明显结果中不会有超过20个警报。但也许这有助于优化器看到这一点。我想这值得一试。)

© www.soinside.com 2019 - 2024. All rights reserved.