Почему array_agg () медленнее, чем неагрегированный конструктор ARRAY ()?

13

Я просто просматривал какой-то старый код, написанный для PostgreSQL до 8.4 , и увидел что-то действительно изящное. Я помню, что когда-то пользовательские функции делали это раньше, но я забыл, как они array_agg()выглядели. Для обзора современная агрегация написана так.

SELECT array_agg(x ORDER BY x DESC) FROM foobar;

Однако когда-то это было написано так,

SELECT ARRAY(SELECT x FROM foobar ORDER BY x DESC);

Итак, я попробовал это с некоторыми тестовыми данными ..

CREATE TEMP TABLE foobar AS
SELECT * FROM generate_series(1,1e7)
  AS t(x);

Результаты были удивительными. Способ #OldSchoolCool был значительно быстрее: ускорение на 25%. Более того, упрощая его без ЗАКАЗА, показала такую ​​же медлительность.

# EXPLAIN ANALYZE SELECT ARRAY(SELECT x FROM foobar);
                                                         QUERY PLAN                                                          
-----------------------------------------------------------------------------------------------------------------------------
 Result  (cost=104425.28..104425.29 rows=1 width=0) (actual time=1665.948..1665.949 rows=1 loops=1)
   InitPlan 1 (returns $0)
     ->  Seq Scan on foobar  (cost=0.00..104425.28 rows=6017728 width=32) (actual time=0.032..716.793 rows=10000000 loops=1)
 Planning time: 0.068 ms
 Execution time: 1671.482 ms
(5 rows)

test=# EXPLAIN ANALYZE SELECT array_agg(x) FROM foobar;
                                                        QUERY PLAN                                                         
---------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=119469.60..119469.61 rows=1 width=32) (actual time=2155.154..2155.154 rows=1 loops=1)
   ->  Seq Scan on foobar  (cost=0.00..104425.28 rows=6017728 width=32) (actual time=0.031..717.831 rows=10000000 loops=1)
 Planning time: 0.054 ms
 Execution time: 2174.753 ms
(4 rows)

Итак, что здесь происходит. Почему array_agg , внутренняя функция, намного медленнее, чем SQL-планировщик voodoo?

Использование « PostgreSQL 9.5.5 для x86_64-pc-linux-gnu, скомпилированного gcc (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005, 64-bit»

Эван Кэрролл
источник

Ответы:

17

В конструкторе ARRAY нет ничего «старого» или «устаревшего» (вот что ARRAY(SELECT x FROM foobar)). Это современно как всегда. Используйте его для простой агрегации массивов.

Руководство:

Также возможно построить массив из результатов подзапроса. В этой форме конструктор массива записывается с ключевым словом, ARRAYза которым следует подзапрос в скобках (без скобок).

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

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

Эрвин Брандштеттер
источник
array_aggдолжен отслеживать порядок своих входных данных, когда ARRAYконструктор, похоже, выполняет что-то примерно эквивалентное UNIONвнутреннему выражению a . Если бы мне пришлось рисковать, array_aggскорее всего, потребовалось бы больше памяти. Я не смог полностью это проверить, но на PostgreSQL 9.6, работающем на Ubuntu 16.04, ARRAY()запрос с ORDER BYиспользованием внешнего слияния выполнялся медленнее, чем array_aggзапрос. Как вы сказали, если вы не читаете код, ваш ответ - лучшее объяснение, которое у нас есть.
Джефф
@Jeffrey: Вы нашли тестовый случай , когда array_agg()это быстрее , чем конструктор массива? Для простого случая? Очень маловероятно, но если это так, вероятно, потому что Postgres основывала свое решение для плана запросов на неточной статистике настроек стоимости. Я никогда не видел, чтобы array_agg()превзойти конструктор массива, и я проверял много раз.
Эрвин Брандштеттер
1
@ Джеффри: Нет вводящих в заблуждение эффектов кэширования? Вы выполняли каждый запрос более одного раза? Мне нужно увидеть определение таблицы, количество элементов и точный запрос, чтобы сказать больше.
Эрвин Брандштеттер
1
Это не настоящий ответ. Многие универсальные инструменты могут работать так же хорошо, как и более специфические инструменты. Если универсальность действительно делает ее медленнее, то какова ее универсальность?
Гэвин Уол
1
@ Джеффри: Похоже, Postgres выбирает разные алгоритмы сортировки для каждого варианта (на основе оценок стоимости и статистики таблиц). И в итоге выбирается худший метод для конструктора ARRAY, который указывает на то, что один или несколько факторов в расчете оценочной стоимости слишком далеки. Это на временном столе? Вы сделали VACUUM ANALYZEэто до того, как запустили запросы? Рассмотрим: dba.stackexchange.com/a/18694/3684
Эрвин Брандштеттер
5

Я считаю, что принятый Эрвином ответ может быть дополнен следующим.

Обычно мы работаем с обычными таблицами с индексами, а не с временными таблицами (без индексов), как в исходном вопросе. Полезно отметить, что агрегаты, такие как ARRAY_AGG, не могут использовать существующие индексы, когда сортировка выполняется во время агрегации .

Например, предположим следующий запрос:

SELECT ARRAY(SELECT c FROM t ORDER BY id)

Если у нас есть индекс t(id, ...), он может быть использован для последовательного сканирования с tпоследующей сортировкой t.id. Кроме того, если выходной столбец, заключенный в массив (здесь c), является частью индекса (такого как индекс t(id, c)или включаемый индекс t(id) include(c)), это может быть даже сканирование только по индексу.

Теперь давайте перепишем этот запрос следующим образом:

SELECT ARRAY_AGG(c ORDER BY id) FROM t

Теперь агрегация не будет использовать индекс и должна сортировать строки в памяти (или, что еще хуже, для больших наборов данных на диске). Это всегда будет последовательное сканирование с tпоследующим агрегированием + сортировка .

Насколько я знаю, это не задокументировано в официальной документации, но может быть получено из источника. Это должно относиться ко всем текущим версиям, включая v11.

pbillen
источник
2
Хорошая точка зрения. Но по справедливости, запросы с array_agg()или аналогичными агрегатными функциями может еще рычаги индексы с подзапросом , как: SELECT ARRAY_AGG(c) FROM (SELECT c FROM t ORDER BY id) sub. Предложение по агрегату ORDER BY- это то, что исключает использование индекса в вашем примере. Конструктор массива быстрее, чем array_agg()когда любой из них может использовать тот же индекс (или ни один). Это просто не так универсально. См .: dba.stackexchange.com/a/213724/3684
Эрвин Брандштеттер,
1
Да, это важное различие. Я немного изменил свой ответ, чтобы прояснить, что это замечание имеет место только тогда, когда функция агрегирования должна сортировать. Вы действительно можете получить прибыль от индекса в простом случае, потому что PostgreSQL, похоже, дает некоторую гарантию того, что агрегация произойдет в том же порядке, как определено в подзапросе, как объяснено в ссылке. Это довольно круто. Мне интересно, однако, сохраняется ли это в случае секционированных таблиц и / или таблиц FDW и / или параллельных рабочих - и сможет ли PostgreSQL выполнить это обещание в будущих выпусках.
Пбиллен
Для протокола, я ни в коем случае не собирался сомневаться в принятом ответе. Я только думал, что это хорошее дополнение к рассуждению о существовании и использовании индексов в сочетании с агрегацией.
pbillen
1
Это является хорошим дополнением.
Эрвин Брандштеттер