Запрос Postgres медленный со многими столбцами - PullRequest
0 голосов
/ 26 января 2019

У меня есть запрос (выполненный к базе данных Postgres (9.6.10) с использованием psql), который возвращает 10 строк.Запрос выполняется в 20x медленнее при выборе 30 столбцов, а не 1.

Мне кажется, я знаю, почему это происходит (см. ОБЪЯСНИТЕ Вывод ниже).И я предполагаю, что обходной путь состоит в том, чтобы выбрать только идентификатор, а затем снова присоединиться к данным.Это предполагает ошибку в планировщике запросов?Любой другой обходной путь?

Запрос 1 (выполняется за 20 секунд)

EXPLAIN ANALYZE SELECT fundraisers.*
FROM fundraisers 
INNER JOIN audit_logs ON audit_logs.fundraiser_id = fundraisers.id 
LEFT OUTER JOIN accounts ON accounts.id = fundraisers.account_id 
GROUP BY accounts.id, fundraisers.id  
LIMIT 10

Запрос 2 (выполняется за 1 секунду)

Отличается только в выбранных столбцах

EXPLAIN ANALYZE SELECT fundraisers.id
FROM fundraisers 
INNER JOIN audit_logs ON audit_logs.fundraiser_id = fundraisers.id 
LEFT OUTER JOIN accounts ON accounts.id = fundraisers.account_id 
GROUP BY accounts.id, fundraisers.id  
LIMIT 10

EXPLAIN Output

Я заметил, что в выводе EXPLAIN я вижучто Hash Join имеет различную стоимость из-за ширины данных, которые объединяются.то есть.

->  Hash Join  (cost=25967.06..109216.83 rows=1359646 width=1634) (actual time=322.987..1971.464 rows=1356192 loops=1)

против

->  Hash Join  (cost=14500.06..74422.83 rows=1359646 width=8) (actual time=111.710..730.736 rows=1356192 loops=1)

Подробнее

database=# EXPLAIN ANALYZE SELECT fundraisers.*
database-# FROM fundraisers 
database-# INNER JOIN audit_logs ON audit_logs.fundraiser_id = fundraisers.id 
database-# LEFT OUTER JOIN accounts ON accounts.id = fundraisers.account_id 
database-# GROUP BY accounts.id, fundraisers.id  
database-# LIMIT 10;
                                                                       QUERY PLAN                                                                        
---------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=3147608.91..3147608.98 rows=10 width=1634) (actual time=20437.137..20437.190 rows=10 loops=1)
   ->  Group  (cost=3147608.91..3157806.25 rows=1359646 width=1634) (actual time=20437.136..20437.186 rows=10 loops=1)
         Group Key: accounts.id, fundraisers.id
         ->  Sort  (cost=3147608.91..3151008.02 rows=1359646 width=1634) (actual time=20437.133..20437.165 rows=120 loops=1)
               Sort Key: accounts.id, fundraisers.id
               Sort Method: external merge  Disk: 1976192kB
               ->  Hash Join  (cost=25967.06..109216.83 rows=1359646 width=1634) (actual time=322.987..1971.464 rows=1356192 loops=1)
                     Hash Cond: (audit_logs.fundraiser_id = fundraisers.id)
                     ->  Seq Scan on audit_logs  (cost=0.00..40634.14 rows=1517914 width=4) (actual time=0.078..324.638 rows=1517915 loops=1)
                     ->  Hash  (cost=13794.41..13794.41 rows=56452 width=1634) (actual time=321.869..321.869 rows=56452 loops=1)
                           Buckets: 4096  Batches: 32  Memory Usage: 2786kB
                           ->  Hash Left Join  (cost=1548.76..13794.41 rows=56452 width=1634) (actual time=16.465..122.406 rows=56452 loops=1)
                                 Hash Cond: (fundraisers.account_id = accounts.id)
                                 ->  Seq Scan on fundraisers  (cost=0.00..11546.52 rows=56452 width=1630) (actual time=0.068..54.434 rows=56452 loops=1)
                                 ->  Hash  (cost=965.56..965.56 rows=46656 width=4) (actual time=16.337..16.337 rows=46656 loops=1)
                                       Buckets: 65536  Batches: 1  Memory Usage: 2153kB
                                       ->  Seq Scan on accounts  (cost=0.00..965.56 rows=46656 width=4) (actual time=0.020..8.268 rows=46656 loops=1)

 Planning time: 0.748 ms
 Execution time: 21013.427 ms
(19 rows)

database=# EXPLAIN ANALYZE SELECT fundraisers.id
database-# FROM fundraisers 
database-# INNER JOIN audit_logs ON audit_logs.fundraiser_id = fundraisers.id 
database-# LEFT OUTER JOIN accounts ON accounts.id = fundraisers.account_id 
database-# GROUP BY accounts.id, fundraisers.id  
database-# LIMIT 10;
                                                                      QUERY PLAN                                                                      
------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=231527.41..231527.48 rows=10 width=8) (actual time=1314.884..1314.917 rows=10 loops=1)
   ->  Group  (cost=231527.41..241724.75 rows=1359646 width=8) (actual time=1314.884..1314.914 rows=10 loops=1)
         Group Key: accounts.id, fundraisers.id
         ->  Sort  (cost=231527.41..234926.52 rows=1359646 width=8) (actual time=1314.883..1314.901 rows=120 loops=1)
               Sort Key: accounts.id, fundraisers.id
               Sort Method: external merge  Disk: 23840kB
               ->  Hash Join  (cost=14500.06..74422.83 rows=1359646 width=8) (actual time=111.710..730.736 rows=1356192 loops=1)
                     Hash Cond: (audit_logs.fundraiser_id = fundraisers.id)
                     ->  Seq Scan on audit_logs  (cost=0.00..40634.14 rows=1517914 width=4) (actual time=0.062..224.307 rows=1517915 loops=1)
                     ->  Hash  (cost=13794.41..13794.41 rows=56452 width=8) (actual time=111.566..111.566 rows=56452 loops=1)
                           Buckets: 65536  Batches: 1  Memory Usage: 2687kB
                           ->  Hash Left Join  (cost=1548.76..13794.41 rows=56452 width=8) (actual time=17.362..98.257 rows=56452 loops=1)
                                 Hash Cond: (fundraisers.account_id = accounts.id)
                                 ->  Seq Scan on fundraisers  (cost=0.00..11546.52 rows=56452 width=8) (actual time=0.067..54.676 rows=56452 loops=1)
                                 ->  Hash  (cost=965.56..965.56 rows=46656 width=4) (actual time=16.524..16.524 rows=46656 loops=1)
                                       Buckets: 65536  Batches: 1  Memory Usage: 2153kB
                                       ->  Seq Scan on accounts  (cost=0.00..965.56 rows=46656 width=4) (actual time=0.032..7.804 rows=46656 loops=1)
 Planning time: 0.469 ms
 Execution time: 1323.349 ms

Ответы [ 2 ]

0 голосов
/ 26 января 2019

первый:

  • таблицы без ключей не имеют значения (это следствие второй нормальной формы)
  • (результат) запроса к такой таблице не имеет значения
  • без какой-либо структуры (PK, FK, вторичные индексы) оптимизатор имеет только две опции: вложенные циклы (в seqscans) или hashjoins
  • хеш-джоины всегда хороший выбор, при достаточном количестве памяти
  • окончательный ORDER BY или GROUP BY нуждается в шаге сортировки (результат hashjoin не имеет неявного порядка) по complete результирующему набору (только для поиска 10 лучших результатов)
  • если хеш-таблица станет слишком большой (больше WORK_MEM), она попадет на диск
  • большему количеству столбцов требуется больше места, даже в хеш-таблицах, поэтому они превысят WORK_MEM раньше и попадут на диск

Наконец: нет смысла сравнивать и сравнивать бессмысленные запросы. Весь механизм оптимизатора предполагает нормальные модели данных. Без этого он просто производит что-то , которое работает .

0 голосов
/ 26 января 2019

То, что вам не хватает в вашем анализе - это стоимость сортировки.

Последовательность происходящего:

  1. Выбор данных из таблиц + JOIN (достаточно дорого)
  2. Сортировка данных при подготовке к GROUP BY.
  3. GROUP BY (дешево благодаря сорту) + LIMIT по запросу.

Я не могу достать документацию для этого sort, поэтому я предполагаю, что она работает как другая СУБД, которую я немного знаю: Oracle.

Как объяснено здесь , серверу иногда потребуется использовать жесткий диск для этой операции.
Это очень медленная операция.

Скорее всего, это то, что происходит с вашими запросами, с той разницей, что postgresql будет иметь либо 1 поле (= общее время выполнения 1 секунда), либо много (= общее время выполнения 20 секунд).


Учитывая все сказанное, имейте в виду, что вы работаете только с тестовым запросом, вероятно, эквивалентным SELECT * FROM fundraisers LIMIT 10 (основываясь на именах полей, у меня нет определения таблиц, чтобы быть уверенным).

Я не слишком шокирован тем, что при таком разрыве между тем, что вы хотите (= рабочий запрос) и тем, что вы вводите (= тестовый запрос), БД ведет себя немного забавно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...