Чому array_agg () повільніше, ніж неагрегатний конструктор ARRAY ()?


14

Я щойно переглядав старий код, написаний для до-8.4 PostgreSQL , і побачив щось справді чудове. Я пам’ятаю, як користувальницька функція робила щось із цього дня, але я забула, як це було раніше 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 планувальника?

Використання " PostgreSQL 9.5.5 для x86_64-pc-linux-gnu, складеного gcc (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005, 64-розрядний"

Відповіді:


17

Немає нічого "старої школи" чи "застарілої" про конструктора ARRAY (Ось що ARRAY(SELECT x FROM foobar)є). Сучасна як ніколи. Використовуйте його для простого агрегування масиву.

Посібник:

Можна також побудувати масив з результатів підзапиту. У цій формі конструктор масиву записується з ключовим словом, ARRAYза яким йде скобний (не скоплений) підзапит.

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

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

Одна помітна різниця: конструктор ARRAY повертає порожній масив ( {}), якщо жодні рядки не кваліфікуються. array_agg()повертає NULLза те саме.


6

Я вважаю, що прийняту відповідь Ервіна можна додати з наступним.

Зазвичай ми працюємо зі звичайними таблицями з індексами, а не з тимчасовими таблицями (без індексів), як у початковому запитанні. Корисно зазначити, що агрегації, такі як, наприклад 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.


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

Для запису я жодним чином не мав наміру сумніватися у прийнятій відповіді. Я лише думав, що це гарне доповнення до міркувань про існування та використання індексів у поєднанні з агрегацією.
pbillen

1
Це є хорошим доповненням.
Ервін Брандстеттер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.