Use Cases (Atualizado: 01/06/2026)

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.

Otimização SQL com Claude Code: guia prático

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 usoSintomaMelhor melhoriaRisco
Listas administrativasbusca lenta e páginas profundas lentasíndices compostos, paginação keyset, menos colunasfiltros opcionais demais podem multiplicar índices
SaaS ou ecommerceAPI de listagem piora com aumento de tráfegoíndices alinhados a WHERE e ORDER BY, cache, agregações assíncronasnão remover filtros de tenant, estoque ou permissão
Dashboardsgráficos diários ou mensais varrem tabelas grandestabelas de resumo, views materializadas, refresh agendadodefinir 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 include do 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.

#Claude Code #SQL #banco de dados #performance #otimização
Grátis

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.

Masa

Sobre o autor

Masa

Engenheiro focado em workflows práticos com Claude Code.