O que EXPLAIN e EXPLAIN ANALYZE mostram
Uma camada diferente de um log de consultas repetidas
"Querying: detecção N+1" ensinou a ler o log/development.log deste app atrás de uma rajada de consultas repetidas. Era um rótulo que aparece dezenas de vezes, igual exceto por um id que varia. Isso é um padrão entre várias consultas. Este curso começa um nível abaixo, no que o plano de execução de uma única consulta diz quando você a roda uma vez e lê com atenção. Um padrão de log nunca mostra essa camada. Um não substitui o outro. Uma rajada no log diz que um formato de consulta rodou vezes demais; um plano diz se cada uma dessas execuções fez o trabalho de forma eficiente.
EXPLAIN: o plano, sem executar nada
O EXPLAIN pergunta ao planejador de consultas do Postgres o que ele faria com um comando SQL e imprime a árvore de operações escolhida, sem executar nenhuma delas. Todo número de um EXPLAIN simples é uma estimativa, feita a partir de estatísticas que o Postgres guarda sobre cada tabela, e nunca uma medição. Por isso é seguro rodá-lo sobre uma escrita, um UPDATE ou DELETE, sem que a escrita aconteça.
EXPLAIN ANALYZE: o plano, mais o que aconteceu
O EXPLAIN ANALYZE planeja a consulta do mesmo jeito, depois a executa, medindo o tempo de cada operação e contando as linhas que cada uma produziu. A saída mantém a mesma árvore, com as estimativas de custo, e acrescenta as medições actual time=... e rows=... ao lado de cada nó. Este curso usa essa versão do começo ao fim, porque o objetivo é comparar o que o planejador esperava com o que aconteceu. Essa comparação exige que a consulta tenha rodado.
Vale dizer uma consequência com clareza: EXPLAIN ANALYZE num INSERT, UPDATE ou DELETE executa a escrita. Por isso, toda captura deste curso roda uma leitura, e nunca uma escrita.
Rodando contra o banco deste app
Este curso captura cada plano no banco de desenvolvimento deste app pelo bin/rails runner, a mesma ferramenta que "Querying: detecção N+1" usou para capturar a saída do log:
sql = Catalog::Course.active.order(:position).to_sql
plan = ActiveRecord::Base.connection.select_all("EXPLAIN (ANALYZE, BUFFERS) #{sql}").rows
plan.each { |row| puts row.first }
Catalog::Course.active é um scope deste app, scope :active, -> { where(active: true) }. O .to_sql transforma a relação do ActiveRecord no SQL que o Postgres vai rodar. Envolvê-lo em EXPLAIN (ANALYZE, BUFFERS) acrescenta contagens de leitura de páginas, Buffers: shared hit=N, ao lado do tempo. O banco de desenvolvimento roda o PostgreSQL 18, enquanto a produção roda a imagem postgres:16. As versões mais novas imprimem alguns detalhes a mais (contagens rows= com casas decimais, por exemplo). As próximas três aulas rodam esse padrão em três consultas que este app usa e leem a saída.
O que este curso cobre
- A anatomia básica de um plano: tipos de scan,
cost=,actual time=erows=, lidos numa consulta à tabelacoursesdeste app. - Um caso em que um índice é usado e o Postgres ainda acrescenta uma etapa de ordenação separada, e por quê.
- Ler um join e uma agregação juntos, numa consulta de ranking entre domínios que este app rodava em toda página de perfil, depois substituída por snapshots pré-calculados.
- Um caso pego ao vivo em que a estimativa de linhas do planejador estava errada, e o que a corrige.
- Pedir a uma ferramenta de IA que leia bem um plano, a partir da disciplina de "colar, não parafrasear" dos cursos de IA anteriores.
- Juntar o fluxo, de um plano capturado bruto a uma conclusão verificada.
Continue com a aula 2
Lendo a anatomia de um plano: tipos de scan, custo, e linhas
Lê o plano da consulta de cursos ativos: um Seq Scan, um Sort, estimativas de custo e linhas medidas.
Com uma conta grátis você também tem os quizzes, seu progresso e suas anotações, e a discussão.