Por que array_agg () é mais lento que o construtor não agregado ARRAY ()?


13

Eu estava revisando algum código antigo escrito para o PostgreSQL anterior à 8.4 e vi algo realmente bacana. Lembro-me de ter uma função personalizada para fazer isso antes, mas esqueci como array_agg()era. Para revisão, a agregação moderna é escrita assim.

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

No entanto, era uma vez, foi escrito assim,

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

Então, eu tentei com alguns dados de teste ..

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

Os resultados foram surpreendentes. A maneira #OldSchoolCool foi massivamente mais rápida: uma aceleração de 25%. Além disso, simplificá-lo sem a ORDEM, mostrou a mesma lentidão.

# 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)

Então, o que está acontecendo aqui. Por que array_agg , uma função interna é muito mais lenta que o vodu SQL do planejador?

Usando o " PostgreSQL 9.5.5 no x86_64-pc-linux-gnu, compilado pelo gcc (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005, 64 bits"

Respostas:


17

Não há nada "antiquado" ou "desatualizado" em um construtor ARRAY (é isso que ARRAY(SELECT x FROM foobar)é). É moderno como sempre. Use-o para agregação de matriz simples.

O manual:

Também é possível construir uma matriz a partir dos resultados de uma subconsulta. Nesse formulário, o construtor de matriz é escrito com a palavra-chave ARRAYseguida por uma subconsulta entre parênteses (sem colchetes).

A função agregadaarray_agg() é muito mais versátil, pois pode ser integrada em SELECTlista com mais colunas, possivelmente mais agregações na mesma SELECT, e grupos arbitrários podem ser formados com GROUP BY. Enquanto um construtor ARRAY pode retornar apenas uma única matriz de SELECTuma única coluna retornada.

Não estudei o código fonte, mas parece óbvio que uma ferramenta muito mais versátil também é mais cara.


array_aggdeve acompanhar a ordem de suas entradas em que o ARRAYconstrutor parece estar fazendo algo aproximadamente equivalente a a UNIONcomo uma expressão internamente. Se eu tivesse que arriscar um palpite, array_aggprovavelmente exigiria mais memória. Não pude testar exaustivamente isso, mas no PostgreSQL 9.6 executando no Ubuntu 16.04, a ARRAY()consulta ORDER BYusou uma mesclagem externa e foi mais lenta que a array_aggconsulta. Como você disse, antes de ler o código, sua resposta é a melhor explicação que temos.
Jeff

@ Jeffrey: Você encontrou um caso de teste em que array_agg()é mais rápido que o construtor de array? Para um caso simples? Muito improvável, mas, provavelmente, porque o Postgres baseou sua decisão para um plano de consulta em estatísticas imprecisas das configurações de custo. Eu nunca vi array_agg()superar um construtor de matriz e testei várias vezes.
Erwin Brandstetter

1
@ Jeffrey: Sem efeitos de cache enganosos? Você executou cada consulta mais de uma vez? Eu precisaria ver a definição da tabela, cardinalidades e consulta exata para dizer mais.
Erwin Brandstetter

1
Esta não é uma resposta real. Muitas ferramentas versáteis podem funcionar tão bem quanto ferramentas mais específicas. Se ser versátil é, de fato, o que a torna mais lenta, que tal sua versatilidade?
precisa saber é o seguinte

1
@ Jeffrey: Parece que o Postgres escolhe um algoritmo de classificação diferente para cada variante (com base em estimativas de custo e estatísticas de tabela). E acaba escolhendo um método inferior para o construtor ARRAY, o que indica que um ou mais fatores no cálculo do custo estimado estão muito distantes. Isso está em uma tabela temporária? Você fez VACUUM ANALYZEisso antes de executar as consultas? Considere: dba.stackexchange.com/a/18694/3684
Erwin Brandstetter

5

Acredito que a resposta aceita por Erwin possa ser adicionada com o seguinte.

Normalmente, estamos trabalhando com tabelas regulares com índices, em vez de tabelas temporárias (sem índices), como na pergunta original. É útil observar que agregações, como ARRAY_AGG, não podem alavancar índices existentes quando a classificação é feita durante a agregação .

Por exemplo, assuma a seguinte consulta:

SELECT ARRAY(SELECT c FROM t ORDER BY id)

Se tivermos um índice ativado t(id, ...), o índice poderá ser usado, em favor de uma varredura sequencial ativada tseguida por uma classificação ativada t.id. Além disso, se a coluna de saída envolvida na matriz (aqui c) fizer parte do índice (como um índice ativado t(id, c)ou um índice incluído t(id) include(c)), isso pode até ser uma varredura apenas de índice.

Agora, vamos reescrever essa consulta da seguinte maneira:

SELECT ARRAY_AGG(c ORDER BY id) FROM t

Agora, a agregação não usará o índice e precisará classificar as linhas na memória (ou pior ainda, para grandes conjuntos de dados, em disco). Essa sempre será uma varredura seqüencial tseguida de agregação + classificação .

Até onde eu sei, isso não está documentado na documentação oficial, mas pode ser derivado da fonte. Esse deve ser o caso de todas as versões atuais, incluindo a v11.


2
Bom ponto. Mas com toda a justiça, consulta com array_agg()ou funções agregadas semelhantes ainda pode índices de alavancagem com uma subconsulta como: SELECT ARRAY_AGG(c) FROM (SELECT c FROM t ORDER BY id) sub. A ORDER BYcláusula por agregação é o que impede o uso do índice no seu exemplo. Um construtor de matriz é mais rápido do que array_agg()quando um deles pode usar o mesmo índice (ou nenhum). Não é tão versátil. Veja: dba.stackexchange.com/a/213724/3684
Erwin Brandstetter

1
Certo, é uma distinção importante a ser feita. Alterei levemente minha resposta para deixar claro que essa observação é válida apenas quando a função de agregação precisa ser classificada. Você ainda pode lucrar com o índice no caso simples, porque o PostgreSQL parece garantir que a agregação ocorrerá na mesma ordem que a definida na subconsulta, conforme explicado no link. Isso é bem legal. Gostaria de saber se isso ainda vale no caso de tabelas particionadas e / ou tabelas FDW e / ou trabalhadores paralelos - e se o PostgreSQL pode manter essa promessa em versões futuras.
pbillen

Para constar, eu não tinha a intenção de duvidar da resposta aceita. Eu apenas pensei que era uma boa adição à razão sobre a existência e o uso de índices em combinação com agregação.
pbillen

1
Ele é um bom complemento.
Erwin Brandstetter
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.