进行内部联接时,索引顺序由慢

时间:2019-04-20 12:33:59

标签: sql postgresql performance

我正在尝试使用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"

3 个答案:

答案 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)

解释似乎很容易。您正在联接两个表AlertsSubscriptionFeed。您想查看具有最高日期的二十个结果行。每个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万行,具体取决于每个警报有多少个订阅。 (不过,我认为每个警报可能有 个订阅,所以不应有那么多行要加入。)最后,我们再次限制结果以便结束不超过二十个。

索引:

  • 警报(创建于desc,id为desc)
  • 订阅供稿(alertid)

(此查询实际上不应与您自己的查询有所不同,因为很明显结果中的警报不能超过20个。但是也许它可以帮助优化器看到这一点。我想值得尝试一下。 )