CommunityPesquisa e Análise de Dadosgithub.com

Ajuste de SQL (PostgreSQL primeiro)

Use quando uma consulta SQL está lenta, um plano mostra Seq Scan ou estimativas ruins de linhas, a saída do EXPLAIN ANALYZE precisa ser interpretada, a escolha de um índice é incerta, a paginação com OFFSET degrada, uma ORM gera consultas N+1, o pg_stat_statements precisa de triagem, ou uma consulta no BigQuery/Snowflake varre demais. PostgreSQL primeiro.

O que é Ajuste de SQL (PostgreSQL primeiro)?

Esta skill descreve um procedimento sistemático para ajuste de desempenho de SQL, com foco principal em PostgreSQL, mas com notas para BigQuery e Snowflake. Ela orienta o usuário a medir antes de alterar, capturar planos de execução com EXPLAIN ANALYZE, e identificar os nós problemáticos usando regras de leitura de planos detalhadas. A skill cobre tópicos como escolha de índice (btree, parcial, de cobertura, de expressão, BRIN, GIN, GiST), sargabilidade, CTEs materializadas, janelas vs. self-joins, e o loop de triagem com pg_stat_statements. Inclui também soluções para problemas comuns como N+1, paginação com keyset, e dicas para carga em massa (bulk ETL). É útil para qualquer desenvolvedor ou DBA que precise otimizar consultas em produção de forma baseada em evidências, suportando as plataformas PostgreSQL, BigQuery e Snowflake.

Funciona com~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/ZrobDev/data-engineering-skills/tree/HEAD/skills/sql-tuning

Installed? Explore more Pesquisa e Análise de Dados skills: affaan-m/uspto-database, affaan-m/scholar-evaluation, affaan-m/literature-review · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

O que Ajuste de SQL (PostgreSQL primeiro) faz?

Meça antes de mudar qualquer coisa. Uma alteração de ajuste sem um plano de antes/depois e medição de tempo é um palpite.

Procedimento

  1. Encontre o alvo: classifique por custo total, não pela chamada individual mais lenta (veja o loop de triagem).
  2. Capture a linha de base: EXPLAIN (ANALYZE, BUFFERS) <consulta> em dados do tamanho da produção. Dados de desenvolvimento pequenos escolhem planos diferentes.
  3. Localize o nó onde o tempo ou as linhas dão errado (regras de leitura abaixo). Apenas corrija aquele nó.
  4. Execute novamente a mesma captura de plano. Mantenha a alteração apenas se ela vencer tanto em tempo quanto em buffers.
  5. Registre o índice ou a reescrita e o motivo, junto à migração de esquema.

Envolva EXPLAIN ANALYZE de INSERT/UPDATE/DELETE em BEGIN; ... ROLLBACK; porque ANALYZE realmente executa a instrução.

Lendo um plano

  • Leia de baixo para cima, nós internos primeiro. O actual time por nó é por iteração; multiplique por loops para o custo real.
  • Compare rows (estimativa) com actual rows. Uma diferença de 10x ou mais é a causa raiz muito mais frequente do que um índice faltando: execute ANALYZE <tabela>, aumente o alvo de estatísticas da coluna, ou adicione estatísticas estendidas (CREATE STATISTICS ... (dependencies, ndistinct)) para colunas correlacionadas.
  • Rows Removed by Filter muito maior que as linhas retornadas significa que o índice (ou a falta dele) não é seletivo para este predicado.
  • Buffers: shared read é disco ou cache do SO; shared hit é shared_buffers. Leituras altas em execuções repetidas significam que o conjunto de trabalho não cabe na memória.
  • Sort Method: external merge Disk: significa que work_mem era muito pequeno para esta ordenação. Aumente por sessão ou por role, não globalmente, porque ele multiplica por nó de ordenação por conexão.
  • Nested Loop com uma contagem grande de loops interna e um lado interno sem índice é o clássico join lento. Hash Join com derramamento (Batches: >1) é a mesma história de work_mem.
  • Seq Scan é correto quando a consulta lê uma grande fração da tabela. Não adicione um índice para remover um Seq Scan que retorna 20% das linhas.
  • Heap Fetches em um Index Only Scan significa que o mapa de visibilidade está desatualizado; execute VACUUM na tabela.

Escolhendo um índice

Indexe apenas o predicado, join ou ORDER BY que você mediu. Cada índice custa tempo de escrita, WAL e trabalho de vacuum.

  • btree: padrão para igualdade, intervalo, ordenação. Ordem das colunas: colunas de igualdade primeiro, depois a coluna de intervalo ou ordenação. Skip scan no PG 18 ajuda alguns casos de coluna líder faltante, mas não projete em torno disso.
  • Parcial (WHERE status = 'open'): quando consultas visam uma fatia pequena e estável (itens abertos, linhas ainda não processadas). O WHERE da consulta deve implicar o predicado do índice.
  • De cobertura (INCLUDE (col)): quando um index-only scan removeria heap fetches para uma consulta quente. Colunas incluídas não são chaves pesquisáveis.
  • De expressão (ON (lower(email)), ON ((payload->>'id'))): quando a consulta aplica uma função à coluna. A consulta deve usar a expressão idêntica.
  • BRIN: para tabelas enormes de somente anexação onde a coluna correlaciona com a ordem física (timestamps, ids seriais). Índice minúsculo, poda grosseira. Verifique pg_stats.correlation perto de 1; se for baixo, BRIN não podará.
  • GIN: contenção jsonb, arrays, texto completo. GiST: intervalos, geometria, restrições de exclusão.
  • Crie em tabelas ativas com CREATE INDEX CONCURRENTLY; não pode ser executado dentro de um bloco de transação e uma execução com falha deixa um índice INVÁLIDO para ser removido.
  • Remova índices não utilizados apenas após verificar pg_stat_user_indexes.idx_scan durante um ciclo completo de negócios (final do mês para cargas financeiras) e também nas réplicas.

Sargabilidade

Um predicado pode usar um índice apenas se a coluna indexada estiver sozinha de um lado.

  • WHERE created_at >= '2026-01-01' AND created_at < '2027-01-01' em vez de WHERE extract(year from created_at) = 2026 ou date_trunc(...) = ....
  • WHERE col = $1 com tipos correspondentes; casts no lado da coluna como numeric_col = '5'::text bloqueiam o índice. Converta o parâmetro, não a coluna.
  • Evite LIKE '%x' com curinga inicial; use pg_trgm GIN ou busca em texto completo.
  • OR entre colunas diferentes geralmente perde o índice; tente UNION ALL de dois ramos indexados.
  • NOT IN (subconsulta) é hostil a NULL e lento; use NOT EXISTS.
  • Prefira EXISTS para semi-joins em vez de DISTINCT após um join, porque ele para na primeira correspondência e não duplica linhas.

Funções janela vs self-joins

Use uma função janela quando o objetivo for um valor por linha a partir de linhas relacionadas no mesmo conjunto: totais acumulados, rank, lag/lead,

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

ZrobDev/data-cleaning

Use when a new CSV, parquet or xlsx needs profiling before touching it, or when cleaning, deduping or normalizing records, coercing dates/currency/IDs, or reconciling a source of truth against a derived file (OCR'd or scanned documents vs a system export, an upload vs the DB). Covers row-count mismatches, lost leading zeros, duplicates, control totals, anti-joins, exceptions reports.

ZrobDev/data-modeling

Use when designing or reviewing an analytical model (star schema, marts, fact/dimension tables), when metrics double-count or inflate after a join, when choosing surrogate vs natural keys or SCD type 1 vs 2, when deciding raw/staging/intermediate/mart layering, naming, or which tests a model needs, and for PostgreSQL OLTP schema review (normalization, FK indexes, multi-tenant keys).

ZrobDev/etl-pipelines

Use when designing, reviewing or debugging batch ETL/ELT in Python - duplicate or missing rows after a rerun, double-counted backfills, watermark gaps, late-arriving data, retry storms, schema drift breaking a load, scrape-to-warehouse pipelines, a job that silently loads zero rows, or choosing between cron, n8n, Airflow and dlt.

ZrobDev/lakehouse

Use when choosing or touching lakehouse storage - Postgres vs warehouse vs lakehouse, Delta vs Iceberg vs DuckLake, slow queries from small parquet files, partitioning, OPTIMIZE/VACUUM/expire_snapshots, MERGE or schema-evolution errors, catalog choice, PySpark collect()/UDF/shuffle/skew, or testing tables locally with DuckDB or polars.

Habilidades Relacionadas