Чрезвычайно медленный запрос PostgreSQL с предложениями ORDER и LIMIT - PullRequest
31 голосов
/ 18 мая 2011

У меня есть таблица, назовем ее «foos», в которой почти 6 миллионов записей. Я выполняю следующий запрос:

SELECT "foos".*
FROM "foos"
INNER JOIN "bars" ON "foos".bar_id = "bars".id
WHERE (("bars".baz_id = 13266))
ORDER BY "foos"."id" DESC
LIMIT 5 OFFSET 0;

Этот запрос выполняется очень долго (время ожидания Rails при его выполнении). Для всех идентификаторов есть индекс. Любопытно, что, если я уберу предложение ORDER BY или предложение LIMIT, оно будет выполнено практически мгновенно.

Я предполагаю, что наличие ORDER BY и LIMIT делает PostgreSQL плохим выбором при планировании запросов. У кого-нибудь есть идеи как это исправить?

Если это поможет, вот EXPLAIN для всех 3 случаев:

//////// Both ORDER and LIMIT
SELECT "foos".*
FROM "foos"
INNER JOIN "bars" ON "foos".bar_id = "bars".id
WHERE (("bars".baz_id = 13266))
ORDER BY "foos"."id" DESC
LIMIT 5 OFFSET 0;
                                                     QUERY PLAN                                                     
--------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.00..16663.44 rows=5 width=663)
   ->  Nested Loop  (cost=0.00..25355084.05 rows=7608 width=663)
         Join Filter: (foos.bar_id = bars.id)
         ->  Index Scan Backward using foos_pkey on foos  (cost=0.00..11804133.33 rows=4963477 width=663)
               Filter: (((NOT privacy_protected) OR (user_id = 67962)) AND ((status)::text = 'DONE'::text))
         ->  Materialize  (cost=0.00..658.96 rows=182 width=4)
               ->  Index Scan using index_bars_on_baz_id on bars  (cost=0.00..658.05 rows=182 width=4)
                     Index Cond: (baz_id = 13266)
(8 rows)

//////// Just LIMIT
SELECT "foos".*
FROM "foos"
INNER JOIN "bars" ON "foos".bar_id = "bars".id
WHERE (("bars".baz_id = 13266))
LIMIT 5 OFFSET 0;
                                                              QUERY PLAN                                                               
---------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.00..22.21 rows=5 width=663)
   ->  Nested Loop  (cost=0.00..33788.21 rows=7608 width=663)
         ->  Index Scan using index_bars_on_baz_id on bars  (cost=0.00..658.05 rows=182 width=4)
               Index Cond: (baz_id = 13266)
         ->  Index Scan using index_foos_on_bar_id on foos  (cost=0.00..181.51 rows=42 width=663)
               Index Cond: (foos.bar_id = bars.id)
               Filter: (((NOT foos.privacy_protected) OR (foos.user_id = 67962)) AND ((foos.status)::text = 'DONE'::text))
(7 rows)

//////// Just ORDER
SELECT "foos".*
FROM "foos"
INNER JOIN "bars" ON "foos".bar_id = "bars".id
WHERE (("bars".baz_id = 13266))
ORDER BY "foos"."id" DESC;
                                                              QUERY PLAN                                                               
---------------------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=36515.17..36534.19 rows=7608 width=663)
   Sort Key: foos.id
   ->  Nested Loop  (cost=0.00..33788.21 rows=7608 width=663)
         ->  Index Scan using index_bars_on_baz_id on bars  (cost=0.00..658.05 rows=182 width=4)
               Index Cond: (baz_id = 13266)
         ->  Index Scan using index_foos_on_bar_id on foos  (cost=0.00..181.51 rows=42 width=663)
               Index Cond: (foos.bar_id = bars.id)
               Filter: (((NOT foos.privacy_protected) OR (foos.user_id = 67962)) AND ((foos.status)::text = 'DONE'::text))
(8 rows)

Ответы [ 4 ]

15 голосов
/ 18 мая 2011

Когда у вас есть как LIMIT, так и ORDER BY, оптимизатор решил, что быстрее пролистывать нефильтрованные записи на foo, нажимая клавишу, пока не получит пять соответствий для остальных критериев. В других случаях он просто выполняет запрос как вложенный цикл и возвращает все записи.

Случайно, я бы сказал, что проблема в том, что PG не справляется с распределением joint различных идентификаторов, и поэтому план настолько неоптимален.

Для возможных решений: я предполагаю, что вы недавно запустили ANALYZE. Если нет, сделайте это. Это может объяснить, почему ваши предполагаемые времена высоки даже на версии, которая быстро возвращается. Если проблема не устранена, возможно, запустите ORDER BY в качестве подвыбора и добавьте LIMIT во внешний запрос.

2 голосов
/ 18 мая 2011

Ваш план запроса указывает фильтр на

(((NOT privacy_protected) OR (user_id = 67962)) AND ((status)::text = 'DONE'::text))

который не появляется в SELECT - откуда он взялся?

Кроме того, обратите внимание, что выражение указано как «Фильтр», а не как «Индексный Конд», который, кажется, указывает, что к нему не применен индекс.

2 голосов
/ 18 мая 2011

Вероятно, это происходит потому, что прежде чем попытаться заказать, затем выбрать.Почему бы не попытаться отсортировать результат во внешнем выделении всех?Что-то вроде: SELECT * FROM (SELECT ... INNER JOIN ETC ...) ORDER BY ... DESC

0 голосов
/ 03 мая 2013

возможно, выполняется полное сканирование таблицы на "foos".Вы пытались изменить порядок таблиц и вместо этого вместо внутреннего соединения использовали левое соединение и посмотрите, быстрее ли оно отобразит результаты.

скажем ...

SELECT "bars"."id", "foos".*
FROM "bars"
LEFT JOIN "foos" ON "bars"."id" = "foos"."bar_id"
WHERE "bars"."baz_id" = 13266
ORDER BY "foos"."id" DESC
LIMIT 5 OFFSET 0;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...