Я просто просматривал какой-то старый код, написанный для 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»
источник
array_agg
должен отслеживать порядок своих входных данных, когдаARRAY
конструктор, похоже, выполняет что-то примерно эквивалентноеUNION
внутреннему выражению a . Если бы мне пришлось рисковать,array_agg
скорее всего, потребовалось бы больше памяти. Я не смог полностью это проверить, но на PostgreSQL 9.6, работающем на Ubuntu 16.04,ARRAY()
запрос сORDER BY
использованием внешнего слияния выполнялся медленнее, чемarray_agg
запрос. Как вы сказали, если вы не читаете код, ваш ответ - лучшее объяснение, которое у нас есть.array_agg()
это быстрее , чем конструктор массива? Для простого случая? Очень маловероятно, но если это так, вероятно, потому что Postgres основывала свое решение для плана запросов на неточной статистике настроек стоимости. Я никогда не видел, чтобыarray_agg()
превзойти конструктор массива, и я проверял много раз.VACUUM ANALYZE
это до того, как запустили запросы? Рассмотрим: dba.stackexchange.com/a/18694/3684Я считаю, что принятый Эрвином ответ может быть дополнен следующим.
Обычно мы работаем с обычными таблицами с индексами, а не с временными таблицами (без индексов), как в исходном вопросе. Полезно отметить, что агрегаты, такие как
ARRAY_AGG
, не могут использовать существующие индексы, когда сортировка выполняется во время агрегации .Например, предположим следующий запрос:
Если у нас есть индекс
t(id, ...)
, он может быть использован для последовательного сканирования сt
последующей сортировкойt.id
. Кроме того, если выходной столбец, заключенный в массив (здесьc
), является частью индекса (такого как индексt(id, c)
или включаемый индексt(id) include(c)
), это может быть даже сканирование только по индексу.Теперь давайте перепишем этот запрос следующим образом:
Теперь агрегация не будет использовать индекс и должна сортировать строки в памяти (или, что еще хуже, для больших наборов данных на диске). Это всегда будет последовательное сканирование с
t
последующим агрегированием + сортировка .Насколько я знаю, это не задокументировано в официальной документации, но может быть получено из источника. Это должно относиться ко всем текущим версиям, включая v11.
источник
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