индексируется порядок медленно при выполнении внутреннего соединения - PullRequest
1 голос
/ 20 апреля 2019

Я пытаюсь объединить две простые таблицы с ORDER BY предложением

Таблица:

Оповещения

  • Всего строк: 690000
  • Индексы: (createdAt DESC, id DESC)

SubscriptionFeed

  • Всего строк: 99990
  • Индексы: (createdAt DESC)

Проблема заключается в том, что при добавлении ORDER BY a."createdAt" DESC, a.id DESC запрос становится намного медленнее, чем при использовании ORDER BY sf."createdAt" DESC

Мне нужен запрос, и он объясняет план

QUERY:

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 и план объяснения

QUERY:

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 ]

1 голос
/ 20 апреля 2019

Объяснение кажется простым. Вы объединяете две таблицы, Alerts и SubscriptionFeed. И вы хотите увидеть двадцать строк результатов с самыми высокими датами. Каждая SubscriptionFeed строка принадлежит Alerts строке, но не каждая Alerts строка обязательно имеет SubscriptionFeed строки.

Итак, если вам нужны последние SubscriptionFeed строки, это легко: возьмите последние 20 SubscriptionFeed строк (из индекса), соедините их 20 Alerts строк, и все готово.

Если вместо этого требуется последняя версия Alerts, СУБД возьмет последнюю строку Alerts, объединит все ее подписки, проверит, получило ли она уже двадцать строк, если нет, то снова возьмет следующую строку Alerts присоединитесь ко всем его подпискам, проверьте, достигнуты ли двадцать строк, и так далее. Что ж, СУБД может использовать другой алгоритм, но он никогда не будет таким простым, как для последних SubscriptionFeed.

Вот и все. Маловероятно, что мы можем получить запрос Alerts почти так же быстро, как запрос SubscriptionFeed. Но мы можем подумать о том, как помочь СУБД в доступе к строкам: Ваш существующий индекс на Alerts(createdAt DESC, id DESC) помогает СУБД быстро находить самые последние строки Alerts. Чтобы быстро получить их SubscriptionFeed, вам нужно индексировать SubscriptionFeed(alertId). (Ну, может быть, у вас уже есть, учитывая, что SubscriptionFeed.alertId ссылки Alerts.id.)

Кроме того, вы можете предоставить покрывающие индексы, содержащие все столбцы из таблицы, которую вы используете в своем запросе (то есть добавить другие столбцы к уже упомянутым индексам), например ::

create index idx on SubscriptionFeed(alertId, name);
1 голос
/ 20 апреля 2019

Я объяснил проблему в моем другом ответе. Вот мысль о том, как ускорить запрос.

Ваш запрос получает последние оповещения с их подписками. Вы сокращаете до двадцати строк результатов, таким образом, возможно, получая несколько случайно выбранных (например, если у двух последних предупреждений было по 15 подписок в каждой, вы должны выбрать все подписки для последнего предупреждения и выбрать пять случайных для другого предупреждения).

Мы не знаем, сколько разных предупреждений будет в результате. Но мы знаем, что никогда не бывает больше 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 или миллион, в зависимости от количества подписок на одно предупреждение. (Однако я считаю вполне вероятным, что это много подозрений на предупреждение, поэтому не должно быть столько строк для объединения.) Наконец, мы снова ограничиваем результат, чтобы в итоге получить не более двадцати.

Индексы:

  • оповещения (созданы на основе идентификатора)
  • подписка (алертид)

(Этот запрос на самом деле не должен иметь никакого значения для вашего собственного запроса, поскольку очевидно, что в результате не может быть более 20 предупреждений. Но, возможно, это поможет оптимизатору увидеть это. Думаю, стоит попробовать. )

1 голос
/ 20 апреля 2019

Это отвечает на оригинальную версию вопроса.

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).
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...