Otimização SQL com Claude Code: guia prático
Otimize SQL com Claude Code: consultas lentas, EXPLAIN, índices, N+1, paginação, agregações, testes e rollout.
O que significa otimizar SQL
Otimização SQL é ajustar consultas, índices e padrões de acesso a dados para devolver o mesmo resultado com menos leituras e menor tempo de espera. Não é apenas escrever uma query mais bonita. Na prática, você precisa olhar o endpoint, as chamadas do ORM, o plano de execução, o volume real de dados, os testes de regressão e o monitoramento depois do rollout.
Claude Code ajuda porque consegue ler o contexto completo: handler da rota, modelos do Prisma ou de outro ORM, migrations, logs de slow query e testes. Em sistemas reais, a lentidão raramente vem de uma única query misteriosa. Normalmente é a soma de SELECT * em telas de lista, N+1, paginação com OFFSET profundo, índices compostos desalinhados e agregações de dashboard recalculadas em toda requisição.
Este guia usa PostgreSQL como referência principal e também vale para MySQL em vários pontos. Para interpretar planos, consulte a documentação oficial: PostgreSQL Using EXPLAIN, PostgreSQL EXPLAIN, PostgreSQL Indexes, PostgreSQL Multicolumn Indexes e MySQL 8.4 Using EXPLAIN.
Para completar o trabalho, veja também design de banco de dados, migração de banco de dados, Prisma ORM e otimização de performance.
Três casos de uso práticos
Comece pela tela ou fluxo de usuário. Isso evita que Claude Code sugira índices genéricos.
| Caso de uso | Sintoma | Melhor melhoria | Risco |
|---|---|---|---|
| Listas administrativas | busca lenta e páginas profundas lentas | índices compostos, paginação keyset, menos colunas | filtros opcionais demais podem multiplicar índices |
| SaaS ou ecommerce | API de listagem piora com aumento de tráfego | índices alinhados a WHERE e ORDER BY, cache, agregações assíncronas | não remover filtros de tenant, estoque ou permissão |
| Dashboards | gráficos diários ou mensais varrem tabelas grandes | tabelas de resumo, views materializadas, refresh agendado | definir antes a exigência de atualização dos dados |
Defina um orçamento antes de editar: p95 abaixo de 300 ms, no máximo cinco comandos SQL por requisição de lista e JSON de resposta compatível.
Prompt para Claude Code:
Otimize esta API de lista para performance SQL.
Meta: p95 abaixo de 300 ms, no máximo 5 comandos SQL, JSON de resposta sem mudanças.
Revise: src/routes/admin/orders.ts, prisma/schema.prisma, migrations, slow-query.log.
Faça: identificar SQL lento, ler EXPLAIN ANALYZE, propor índices, remover N+1, adicionar testes de regressão.
Restrição: manter a mudança pequena e compatível com produção.
Passo 1: capturar consultas lentas
Não crie índices por intuição. Capture primeiro as consultas realmente lentas. No PostgreSQL, pg_stat_statements e logs do banco são bons pontos de partida. Logs do ORM também ajudam porque conectam a SQL ao endpoint que a gerou.
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
SELECT
query,
calls,
ROUND(total_exec_time::numeric, 2) AS total_ms,
ROUND(mean_exec_time::numeric, 2) AS mean_ms,
rows
FROM pg_stat_statements
WHERE query NOT ILIKE '%pg_stat_statements%'
ORDER BY total_exec_time DESC
LIMIT 10;
Com Prisma, você pode registrar consultas lentas em desenvolvimento usando APIs reais do Prisma Client:
import { PrismaClient } from "@prisma/client";
export const prisma = new PrismaClient({
log: [
{ emit: "event", level: "query" },
{ emit: "stdout", level: "error" },
{ emit: "stdout", level: "warn" },
],
});
prisma.$on("query", (event) => {
if (event.duration > 100) {
console.log({
durationMs: event.duration,
query: event.query,
params: event.params,
});
}
});
Passe logs anonimizados para Claude Code. Remova emails, tokens e IDs de clientes, depois peça agrupamento por tela, origem, tempo médio, número de chamadas e causa provável.
Passo 2: ler EXPLAIN e EXPLAIN ANALYZE
Depois de encontrar a query candidata, use EXPLAIN para ver o plano previsto. Em um ambiente seguro com dados representativos, use EXPLAIN (ANALYZE, BUFFERS). Lembre que ANALYZE executa a instrução, então não use sem cuidado em UPDATE ou DELETE.
EXPLAIN (ANALYZE, BUFFERS)
SELECT
o.id,
o.created_at,
o.status,
c.name AS customer_name
FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE o.status = 'paid'
AND o.created_at >= DATE '2026-01-01'
ORDER BY o.created_at DESC, o.id DESC
LIMIT 50;
Nem todo Seq Scan é problema. Em tabelas pequenas ou filtros pouco seletivos, ele pode ser correto. Observe a diferença entre linhas estimadas e reais, sorts grandes, leituras de buffer e ordem dos joins.
Prompt para Claude Code:
Revise este EXPLAIN ANALYZE. Separe a resposta em gargalo, erro de estimativa de linhas, candidatos de índice e reescritas SQL seguras. Não altere o resultado retornado.
Passo 3: desenhar índices compostos
Índices aceleram leitura, mas aumentam custo de escrita, armazenamento e risco de migration. Desenhe a partir das queries reais: condições de igualdade, condições de intervalo, colunas de ordenação e joins.
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_orders_status_created_id
ON orders (status, created_at DESC, id DESC);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_orders_customer_created
ON orders (customer_id, created_at DESC);
No PostgreSQL, CREATE INDEX CONCURRENTLY reduz bloqueios de escrita, mas não pode rodar dentro de uma transação normal. Documente no PR como sua ferramenta de migration vai executar essa etapa.
Antes de pedir sugestões de índice, entregue os índices existentes:
SELECT
indexname,
indexdef
FROM pg_indexes
WHERE schemaname = 'public'
AND tablename = 'orders'
ORDER BY indexname;
Passo 4: eliminar N+1
N+1 acontece quando você busca uma lista e depois consulta dados relacionados para cada item. Isso aumenta trabalho no banco e espera de rede.
// Ruim: uma consulta de customer por order
const orders = await prisma.order.findMany({
take: 100,
orderBy: { createdAt: "desc" },
});
const rows = [];
for (const order of orders) {
const customer = await prisma.customer.findUnique({
where: { id: order.customerId },
});
rows.push({ ...order, customerName: customer?.name ?? "" });
}
// Melhor: buscar apenas as colunas usadas pela UI
const orders = await prisma.order.findMany({
take: 100,
orderBy: { createdAt: "desc" },
select: {
id: true,
createdAt: true,
status: true,
totalAmount: true,
customer: {
select: {
id: true,
name: true,
},
},
},
});
Peça a Claude Code para manter o formato da resposta e reduzir o número de queries. Assim ele precisa ler a UI ou o serializer, em vez de apenas adicionar include amplo.
Passo 5: trocar OFFSET profundo por paginação keyset
OFFSET 10000 LIMIT 50 costuma ficar lento porque o banco ainda percorre as linhas ignoradas. Para feeds cronológicos e scroll infinito, use como cursor o último created_at e id.
SELECT id, created_at, status, total_amount
FROM orders
WHERE status = 'paid'
ORDER BY created_at DESC, id DESC
LIMIT 50;
SELECT id, created_at, status, total_amount
FROM orders
WHERE status = 'paid'
AND (created_at, id) < (TIMESTAMP '2026-05-01 10:30:00', 98765)
ORDER BY created_at DESC, id DESC
LIMIT 50;
O índice (status, created_at DESC, id DESC) combina com esse padrão. O id estabiliza a ordenação quando várias linhas têm o mesmo timestamp.
Passo 6: tirar agregações de requisições críticas
Dashboards ficam lentos quando toda requisição recalcula os mesmos totais. Se o negócio aceita atraso, use uma tabela de resumo ou refresh agendado.
CREATE TABLE IF NOT EXISTS daily_sales_summary (
sales_date date PRIMARY KEY,
order_count integer NOT NULL,
revenue numeric(12, 2) NOT NULL,
updated_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO daily_sales_summary (sales_date, order_count, revenue)
SELECT
created_at::date AS sales_date,
COUNT(*) AS order_count,
SUM(total_amount) AS revenue
FROM orders
WHERE status = 'paid'
AND created_at >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY created_at::date
ON CONFLICT (sales_date) DO UPDATE
SET
order_count = EXCLUDED.order_count,
revenue = EXCLUDED.revenue,
updated_at = now();
Diga a Claude Code qual frescor é aceitável: um minuto, cinco minutos, diário ou tempo real estrito. Sem isso, uma otimização pode mudar o significado do dashboard.
Orçamento de queries e testes
A performance pode regredir. Snapshots exatos de plano são frágeis; comece com número de queries, compatibilidade de resposta e alguns casos representativos.
import { PrismaClient } from "@prisma/client";
import { expect, it } from "vitest";
it("keeps the order list within the query budget", async () => {
const queries: string[] = [];
const prisma = new PrismaClient({
log: [{ emit: "event", level: "query" }],
});
prisma.$on("query", (event) => {
queries.push(event.query);
});
await prisma.order.findMany({
take: 50,
orderBy: [{ createdAt: "desc" }, { id: "desc" }],
select: {
id: true,
createdAt: true,
status: true,
customer: { select: { id: true, name: true } },
},
});
await prisma.$disconnect();
expect(queries.length).toBeLessThanOrEqual(2);
});
Mapa do fluxo
flowchart LR
A["Log de slow query"] --> B["EXPLAIN / EXPLAIN ANALYZE"]
B --> C["Mudanças de índice e SQL"]
C --> D["N+1, paginação, agregação"]
D --> E["Testes de orçamento SQL"]
E --> F["Rollout e monitoramento"]
Armadilhas comuns
- Ler colunas grandes com
SELECT *em páginas de lista. - Esperar muito de um índice apenas em uma coluna de baixa cardinalidade como
status. - Tentar acelerar
LIKE '%keyword%'com índice B-tree comum. - Julgar planos usando dados pequenos de desenvolvimento.
- Rodar migration de índice em horário de pico sem revisar locks, disco e lag de réplicas.
- Usar
includedo ORM e devolver todo o grafo de relações.
Checklist de rollout
1. Endpoint e tela afetada foram identificados.
2. SQL lento veio de logs ou pg_stat_statements.
3. EXPLAIN antes/depois foi salvo.
4. Índices existentes foram revisados.
5. N+1, OFFSET, colunas retornadas e agregações síncronas foram checados.
6. Compatibilidade da resposta foi testada.
7. Teste de número de queries ou latência foi adicionado.
8. Risco de lock, duração e rollback do índice foram documentados.
9. Métricas pós-release foram definidas.
Otimização SQL é um fluxo de trabalho, não um truque isolado. Claude Code conecta código, migrations e testes, mas a decisão final deve vir de dados reais, planos de execução e requisitos do produto.
ClaudeCodeLab oferece treinamento e consultoria baseados nesse tipo de revisão prática. Com um endpoint lento, logs e schema, é possível transformar uma investigação pontual em um checklist SQL reutilizável para sua equipe.
PDF grátis: cheatsheet do Claude Code
Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.
Cuidamos dos seus dados e não enviamos spam.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.
Artigos relacionados
Workflow Obsidian para CLAUDE.md com Claude Code
Transforme notas de trabalho do Obsidian em notas operacionais CLAUDE.md para preservar contexto.
Claude Code Revenue CTA Routing: artigos para PDF, Gumroad e consultoria
Um fluxo com Claude Code para levar leitores ao PDF grátis, Gumroad ou consultoria conforme intenção.
Regras de handoff para equipes com Claude Code: evidências, permissões, rollback e receita
Formato prático para entregar trabalho do Claude Code com prova, permissões, rollback, PDF grátis, Gumroad e consultoria.