Respostas:
Explaining_EXPLAIN.pdf também pode ajudar.
A parte que sempre achei confusa é o custo inicial versus custo total. Procuro no Google sempre que me esqueço, o que me traz de volta aqui, o que não explica a diferença, e é por isso que estou escrevendo esta resposta. Isso é o que eu obtive da documentação do PostgresEXPLAIN
, explicado como eu o entendo.
Aqui está um exemplo de um aplicativo que gerencia um fórum:
EXPLAIN SELECT * FROM post LIMIT 50;
Limit (cost=0.00..3.39 rows=50 width=422)
-> Seq Scan on post (cost=0.00..15629.12 rows=230412 width=422)
Aqui está a explicação gráfica do PgAdmin:
(Quando estiver usando o PgAdmin, você pode apontar o mouse para um componente para ler os detalhes de custo.)
O custo é representado como uma tupla, por exemplo, o custo de LIMIT
é cost=0.00..3.39
e o custo da varredura sequencial post
é cost=0.00..15629.12
. O primeiro número na tupla é o custo inicial e o segundo número é o custo total . Como usei EXPLAIN
e não usei EXPLAIN ANALYZE
, esses custos são estimativas, não medidas reais.
Como complicação, os custos de cada nó "pai" incluem os custos de seus nós filhos. Na representação de texto, a árvore é representada por indentação, por exemplo, LIMIT
é um nó pai e Seq Scan
é seu filho. Na representação do PgAdmin, as setas apontam do filho para o pai - a direção do fluxo de dados - o que pode ser contra-intuitivo se você estiver familiarizado com a teoria dos grafos.
A documentação diz que os custos incluem todos os nós filhos, mas observe que o custo total do pai 3.39
é muito menor do que o custo total do filho 15629.12
. O custo total não é inclusivo porque um componente como LIMIT
não precisa processar toda a sua entrada. Veja o EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 100 AND unique2 > 9000 LIMIT 2;
exemplo na documentação do PostgresEXPLAIN
.
No exemplo acima, o tempo de inicialização é zero para ambos os componentes, porque nenhum dos componentes precisa fazer qualquer processamento antes de começar a gravar linhas: uma varredura sequencial lê a primeira linha da tabela e a emite. O LIMIT
lê sua primeira linha e a emite.
Quando um componente precisaria fazer muito processamento antes de começar a produzir qualquer linha? Existem várias razões possíveis, mas vamos ver um exemplo claro. Aqui está a mesma consulta de antes, mas agora contém uma ORDER BY
cláusula:
EXPLAIN SELECT * FROM post ORDER BY body LIMIT 50;
Limit (cost=23283.24..23283.37 rows=50 width=422)
-> Sort (cost=23283.24..23859.27 rows=230412 width=422)
Sort Key: body
-> Seq Scan on post (cost=0.00..15629.12 rows=230412 width=422)
E graficamente:
Mais uma vez, a varredura sequencial ativada post
não tem custo inicial: ela começa a produzir linhas imediatamente. Mas a classificação tem um custo inicial significativo 23283.24
porque precisa classificar a tabela inteira antes que possa produzir até mesmo uma única linha . O custo total da classificação 23859.27
é apenas um pouco maior do que o custo inicial, refletindo o fato de que, uma vez que todo o conjunto de dados foi classificado, os dados classificados podem ser emitidos muito rapidamente.
Observe que o tempo de inicialização do LIMIT
23283.24
é exatamente igual ao tempo de inicialização do tipo. Isso não ocorre porque LIMIT
ele mesmo tem um alto tempo de inicialização. Na verdade, ele tem tempo de inicialização zero por si só, mas EXPLAIN
acumula todos os custos filhos de cada pai, de modo que o LIMIT
tempo de inicialização inclui a soma dos tempos de inicialização de seus filhos.
Esse acúmulo de custos pode dificultar o entendimento do custo de execução de cada componente individual. Por exemplo, nosso LIMIT
tem tempo de inicialização zero, mas isso não é óbvio à primeira vista. Por esse motivo, várias outras pessoas criaram um link para o explain.depesz.com , uma ferramenta criada por Hubert Lubaczewski (também conhecido como depesz) que ajuda a entender EXPLAIN
, entre outras coisas, subtraindo os custos dos filhos dos custos dos pais. Ele menciona algumas outras complexidades em uma curta postagem no blog sobre sua ferramenta.
Ele é executado do mais recuado para o menos recuado e, acredito, da parte inferior da planta para o topo. (Portanto, se houver duas seções recuadas, a que está mais abaixo na página é executada primeiro e, quando se encontram, a outra é executada e a regra que as une é executada.)
A ideia é que em cada etapa haja 1 ou 2 conjuntos de dados que chegam e são processados por alguma regra. Se for apenas um conjunto de dados, a operação é feita para esse conjunto de dados. (Por exemplo, verifique um índice para descobrir quais linhas você deseja, filtre um conjunto de dados ou classifique-o.) Se dois, os dois conjuntos de dados são as duas coisas que são indentadas posteriormente e são unidas pela regra que você vê. O significado da maioria das regras pode ser razoavelmente fácil de adivinhar (especialmente se você leu um monte de planos de explicação antes), no entanto, você pode tentar verificar itens individuais olhando na documentação ou (mais fácil) apenas jogando a frase em Google junto com algumas palavras-chave como EXPLAIN
.
Obviamente, essa não é uma explicação completa, mas fornece contexto suficiente para que você possa descobrir o que quiser. Por exemplo, considere este plano de um banco de dados real:
explain analyze
select a.attributeid, a.attributevalue, b.productid
from orderitemattribute a, orderitem b
where a.orderid = b.orderid
and a.attributeid = 'display-album'
and b.productid = 'ModernBook';
------------------------------------------------------------------------------------------------------------------------------------------------------------
Merge Join (cost=125379.14..125775.12 rows=3311 width=29) (actual time=841.478..841.478 rows=0 loops=1)
Merge Cond: (a.orderid = b.orderid)
-> Sort (cost=109737.32..109881.89 rows=57828 width=23) (actual time=736.163..774.475 rows=16815 loops=1)
Sort Key: a.orderid
Sort Method: quicksort Memory: 1695kB
-> Bitmap Heap Scan on orderitemattribute a (cost=1286.88..105163.27 rows=57828 width=23) (actual time=41.536..612.731 rows=16815 loops=1)
Recheck Cond: ((attributeid)::text = 'display-album'::text)
-> Bitmap Index Scan on (cost=0.00..1272.43 rows=57828 width=0) (actual time=25.033..25.033 rows=16815 loops=1)
Index Cond: ((attributeid)::text = 'display-album'::text)
-> Sort (cost=15641.81..15678.73 rows=14769 width=14) (actual time=14.471..16.898 rows=1109 loops=1)
Sort Key: b.orderid
Sort Method: quicksort Memory: 76kB
-> Bitmap Heap Scan on orderitem b (cost=310.96..14619.03 rows=14769 width=14) (actual time=1.865..8.480 rows=1114 loops=1)
Recheck Cond: ((productid)::text = 'ModernBook'::text)
-> Bitmap Index Scan on id_orderitem_productid (cost=0.00..307.27 rows=14769 width=0) (actual time=1.431..1.431 rows=1114 loops=1)
Index Cond: ((productid)::text = 'ModernBook'::text)
Total runtime: 842.134 ms
(17 rows)
Tente ler você mesmo e veja se faz sentido.
O que eu li é que o banco de dados primeiro verifica o id_orderitem_productid
índice, usando-o para encontrar as linhas que deseja orderitem
, depois classifica o conjunto de dados usando uma classificação rápida (a classificação usada mudará se os dados não couberem na RAM) e, em seguida, deixa isso de lado.
Em seguida, ele faz orditematt_attributeid_idx
a varredura para encontrar as linhas que deseja orderitemattribute
e, em seguida, classifica esse conjunto de dados usando uma classificação rápida.
Em seguida, ele pega os dois conjuntos de dados e os mescla. (Uma união de mesclagem é uma espécie de operação de "compactação" em que percorre os dois conjuntos de dados classificados em paralelo, emitindo a linha unida quando eles correspondem.)
Como eu disse, você trabalha da parte interna à parte externa, de baixo para cima.
Também está disponível uma ferramenta auxiliar online, Depesz , que destacará onde estão as partes caras dos resultados da análise.
também tem um, aqui estão os mesmos resultados , que para mim tornam mais claro onde está o problema.
O PgAdmin mostrará uma representação gráfica do plano de explicação. Alternar entre os dois pode realmente ajudá-lo a entender o que significa a representação de texto. No entanto, se você apenas deseja saber o que ele está fazendo, pode sempre usar a GUI.
A documentação oficial do PostgreSQL fornece uma explicação interessante e completa sobre como entender a saída de explain.