LIMIT с ORDER BY делает запрос медленным - PullRequest
0 голосов
/ 07 декабря 2018

У меня проблемы с оптимизацией запроса в PostgreSQL 9.5.14.

select *
from file as f
join product_collection pc on (f.product_collection_id = pc.id)
where pc.mission_id = 7
order by f.id asc
limit 100;

Занимает около 100 секунд.Если я отбрасываю предложение limit, требуется около 0,5:

С limit:

explain (analyze,buffers) ... -- query exactly as above
                                                                             QUERY PLAN                                                                             
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.84..859.32 rows=100 width=457) (actual time=102793.422..102856.884 rows=100 loops=1)
   Buffers: shared hit=222430592
   ->  Nested Loop  (cost=0.84..58412343.43 rows=6804163 width=457) (actual time=102793.417..102856.872 rows=100 loops=1)
         Buffers: shared hit=222430592
         ->  Index Scan using file_pkey on file f  (cost=0.57..23409008.61 rows=113831736 width=330) (actual time=0.048..28207.152 rows=55858772 loops=1)
               Buffers: shared hit=55652672
         ->  Index Scan using product_collection_pkey on product_collection pc  (cost=0.28..0.30 rows=1 width=127) (actual time=0.001..0.001 rows=0 loops=55858772)
               Index Cond: (id = f.product_collection_id)
               Filter: (mission_id = 7)
               Rows Removed by Filter: 1
               Buffers: shared hit=166777920
 Planning time: 0.803 ms
 Execution time: 102856.988 ms

Без limit:

=>  explain (analyze,buffers)  ... -- query as above, just without limit
                                                                                 QUERY PLAN                                                                                 
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=20509671.01..20526681.42 rows=6804163 width=457) (actual time=456.175..510.596 rows=142055 loops=1)
   Sort Key: f.id
   Sort Method: quicksort  Memory: 79392kB
   Buffers: shared hit=37956
   ->  Nested Loop  (cost=0.84..16494851.02 rows=6804163 width=457) (actual time=0.044..231.051 rows=142055 loops=1)
         Buffers: shared hit=37956
         ->  Index Scan using product_collection_mission_id_index on product_collection pc  (cost=0.28..46.13 rows=87 width=127) (actual time=0.017..0.101 rows=87 loops=1)
               Index Cond: (mission_id = 7)
               Buffers: shared hit=10
         ->  Index Scan using file_product_collection_id_index on file f  (cost=0.57..187900.11 rows=169535 width=330) (actual time=0.007..1.335 rows=1633 loops=87)
               Index Cond: (product_collection_id = pc.id)
               Buffers: shared hit=37946
 Planning time: 0.807 ms
 Execution time: 569.865 ms

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

Количество элементов:
Таблица file: 113 831 736 строк.
Таблица product_collection: 1370 строк.
Запрос без LIMIT: 142,055 строк.
SELECT count(*) FROM product_collection WHERE mission_id = 7: 87 строк.

Что я пробовал:

  • поиск переполнения стека
  • полный вакуумный анализ
  • создание двух индексов столбцов для file.product_collection_id & file.id.(в каждом затронутом поле уже есть индексы из одного столбца.)
  • создание двух индексов столбцов для file.id & file.product_collection_id.
  • увеличение статистики для file.id & file.product_collection_id,затем повторно проанализируйте.
  • изменив различные настройки планировщика запросов.
  • создав нематериализованные представления.
  • поднимаясь и спускаясь по коридору, бормоча про себя.

Кажется, что ни один из них не может существенно изменить производительность.

Мысли?

ОБНОВЛЕНИЕ с OP:
Проверено это на PostgreSQL9.6 и 10.4, и не обнаружил существенных изменений в планах или производительности.

Однако, достаточно низкое значение random_page_cost - единственный способ повысить производительность при поиске без ограничений.

С помощьюпо умолчанию random_page_cost = 4, без limit:

                                                                           QUERY PLAN                                                                           
----------------------------------------------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=9270013.01..9287875.64 rows=7145054 width=457) (actual time=47782.523..47843.812 rows=145697 loops=1)
   Sort Key: f.id
   Sort Method: external sort  Disk: 59416kB
   Buffers: shared hit=3997185 read=1295264, temp read=7427 written=7427
   ->  Hash Join  (cost=24.19..6966882.72 rows=7145054 width=457) (actual time=1.323..47458.767 rows=145697 loops=1)
         Hash Cond: (f.product_collection_id = pc.id)
         Buffers: shared hit=3997182 read=1295264
         ->  Seq Scan on file f  (cost=0.00..6458232.17 rows=116580217 width=330) (actual time=0.007..17097.581 rows=116729984 loops=1)
               Buffers: shared hit=3997169 read=1295261
         ->  Hash  (cost=23.08..23.08 rows=89 width=127) (actual time=0.840..0.840 rows=87 loops=1)
               Buckets: 1024  Batches: 1  Memory Usage: 15kB
               Buffers: shared hit=13 read=3
               ->  Bitmap Heap Scan on product_collection pc  (cost=4.97..23.08 rows=89 width=127) (actual time=0.722..0.801 rows=87 loops=1)
                     Recheck Cond: (mission_id = 7)
                     Heap Blocks: exact=10
                     Buffers: shared hit=13 read=3
                     ->  Bitmap Index Scan on product_collection_mission_id_index  (cost=0.00..4.95 rows=89 width=0) (actual time=0.707..0.707 rows=87 loops=1)
                           Index Cond: (mission_id = 7)
                           Buffers: shared hit=3 read=3
 Planning time: 0.929 ms
 Execution time: 47911.689 ms

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

1 Ответ

0 голосов
/ 07 декабря 2018

Этот запрос сложнее для планировщика запросов Postgres, чем может показаться.В зависимости от количества элементов, распределения данных, частоты значений, размеров, ... могут преобладать совершенно разные планы запросов, и планировщику сложно прогнозировать, что лучше.Текущие версии Postgres лучше справляются с этим в нескольких аспектах, но все еще трудно оптимизировать.

Поскольку вы извлекаете только относительно небольшое количество строк из product_collection, этот эквивалентный запрос с LIMIT в LATERAL подзапрос следует избегать снижения производительности:

SELECT *
FROM   product_collection pc
CROSS  JOIN LATERAL (
   SELECT *
   FROM   file f  -- big table
   WHERE  f.product_collection_id = pc.id
   ORDER  BY f.id
   LIMIT  100
   ) f
WHERE  pc.mission_id = 7
ORDER  BY f.id
LIMIT  100;

Редактировать: это приводит к плану запроса с explain (analyze,verbose), предоставленным OP:

                                                                         QUERY PLAN                                                                          
-------------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=30524.34..30524.59 rows=100 width=457) (actual time=13.128..13.167 rows=100 loops=1)
   Buffers: shared hit=3213
   ->  Sort  (cost=30524.34..30546.09 rows=8700 width=457) (actual time=13.126..13.152 rows=100 loops=1)
         Sort Key: file.id
         Sort Method: top-N heapsort  Memory: 76kB
         Buffers: shared hit=3213
         ->  Nested Loop  (cost=0.57..30191.83 rows=8700 width=457) (actual time=0.060..9.868 rows=2880 loops=1)
               Buffers: shared hit=3213
               ->  Seq Scan on product_collection pc  (cost=0.00..69.12 rows=87 width=127) (actual time=0.024..0.336 rows=87 loops=1)
                     Filter: (mission_id = 7)
                     Rows Removed by Filter: 1283
                     Buffers: shared hit=13
               ->  Limit  (cost=0.57..344.24 rows=100 width=330) (actual time=0.008..0.071 rows=33 loops=87)
                     Buffers: shared hit=3200
                         ->  Index Scan using file_pc_id_index on file  (cost=0.57..582642.42 rows=169535 width=330) (actual time=0.007..0.065 rows=33 loops=87)
                           Index Cond: (product_collection_id = pc.id)
                           Buffers: shared hit=3200
 Planning time: 0.595 ms
 Execution time: 13.319 ms

Вам нужны эти indexes (также поможет ваш исходный запрос):

CREATE INDEX idx1 ON file (product_collection_id, id);     -- crucial
CREATE INDEX idx2 ON product_collection (mission_id, id);  -- helpful

Вы упомянули:

два столбца индексов на file.id & file.product_collection_id.

и т. Д.Но нам нужно наоборот: id последний.Порядок выражений индекса имеет решающее значение.См .:

Обоснование: только 87 строк из product_collection,мы выбираем только максимум из 87 x 100 = 8700 строк (меньше, если не каждый pc.id имеет 100 строк в таблице file), которые затем сортируются перед выбором 100 лучших. Производительность снижается счисло строк, которое вы получаете от product_collection и с большим LIMIT.

С индексом из нескольких столбцов idx1 выше, это 87 быстрых просмотров индекса.Остальное не очень дорого.

Возможна дополнительная оптимизация в зависимости от дополнительной информации.Связанный:

...