Use Cases (Actualizado: 1/6/2026)

Optimización SQL con Claude Code: guía práctica

Optimiza SQL con Claude Code: consultas lentas, EXPLAIN, índices, N+1, paginación, agregaciones, pruebas y despliegue.

Optimización SQL con Claude Code: guía práctica

Qué significa optimizar SQL

Optimizar SQL significa ajustar consultas, índices y formas de acceso a datos para devolver el mismo resultado con menos lecturas y menos tiempo de espera. No se trata de escribir una consulta más ingeniosa. En la práctica hay que revisar el endpoint, las llamadas del ORM, el plan de ejecución, el volumen de datos real, las pruebas de regresión y el monitoreo después del despliegue.

Claude Code ayuda porque puede leer más que una cadena SQL: handlers, modelos de Prisma u otro ORM, migraciones, logs de consultas lentas y pruebas. En sistemas reales, la latencia suele venir de detalles acumulados: SELECT * en listados, bucles N+1, paginación OFFSET profunda, índices compuestos mal alineados y agregaciones de dashboards que se recalculan en cada request.

Esta guía usa PostgreSQL como base y menciona MySQL cuando aplica. Para interpretar planes, revisa la documentación oficial: PostgreSQL Using EXPLAIN, PostgreSQL EXPLAIN, PostgreSQL Indexes, PostgreSQL Multicolumn Indexes y MySQL 8.4 Using EXPLAIN.

También son útiles las guías de diseño de bases de datos, migraciones, Prisma ORM y optimización de rendimiento.

Tres casos de uso

Empieza por la pantalla o flujo de usuario. Así Claude Code puede proponer cambios concretos.

Caso de usoSíntomaMejora principalRiesgo
Listados de administraciónbúsqueda lenta y páginas profundas lentasíndices compuestos, keyset pagination, menos columnasdemasiados filtros opcionales crean demasiados índices
SaaS o ecommercela API de listado se degrada con más tráficoíndices alineados con WHERE y ORDER BY, caché, agregaciones asíncronasno perder filtros de tenant, stock o permisos
Dashboardsgráficos diarios o mensuales escanean tablas grandestablas resumen, vistas materializadas, refresh programadoacordar antes la frescura de los datos

Define un presupuesto antes de tocar código: p95 bajo 300 ms, máximo cinco sentencias SQL por listado y JSON de respuesta compatible.

Prompt para Claude Code:
Optimiza esta API de listado para rendimiento SQL.
Objetivo: p95 bajo 300 ms, máximo 5 sentencias SQL, JSON de respuesta sin cambios.
Revisa: src/routes/admin/orders.ts, prisma/schema.prisma, migrations, slow-query.log.
Haz: identificar SQL lento, leer EXPLAIN ANALYZE, proponer índices, eliminar N+1, agregar pruebas de regresión.
Restricción: cambios pequeños y compatibles con producción.

Paso 1: Capturar consultas lentas

No diseñes índices de memoria. Primero captura las consultas que realmente son lentas. En PostgreSQL puedes usar pg_stat_statements o logs de la base. Los logs del ORM también sirven porque conectan la consulta con el endpoint que la generó.

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;

En Prisma puedes registrar consultas lentas en desarrollo con APIs reales del cliente:

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,
    });
  }
});

Pasa a Claude Code logs anonimizados. Oculta correos, tokens e identificadores de clientes, y pídele agrupar patrones por pantalla, origen, tiempo medio, número de llamadas y causa probable.

Paso 2: Leer EXPLAIN y EXPLAIN ANALYZE

Cuando tengas una consulta candidata, usa EXPLAIN para ver el plan previsto. En un entorno seguro con datos representativos, usa EXPLAIN (ANALYZE, BUFFERS). Recuerda que ANALYZE ejecuta la sentencia, así que no lo uses sin cuidado con UPDATE o 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;

No todo Seq Scan es malo. En tablas pequeñas o filtros poco selectivos puede ser correcto. Observa la diferencia entre filas estimadas y reales, sorts grandes, lecturas de buffers y orden de joins.

Prompt para Claude Code:
Revisa este EXPLAIN ANALYZE. Divide la respuesta en cuello de botella, error de estimación de filas, candidatos de índice y reescrituras SQL seguras. No cambies el resultado.

Paso 3: Diseñar índices compuestos

Los índices aceleran lecturas, pero aumentan coste de escritura, almacenamiento y riesgo de migración. Diseña desde consultas reales: condiciones de igualdad, condiciones de rango, columnas de ordenación y necesidades de join.

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);

En PostgreSQL, CREATE INDEX CONCURRENTLY reduce bloqueos de escritura, pero no puede ejecutarse dentro de una transacción normal. Documenta cómo lo manejará tu herramienta de migración.

Antes de pedir sugerencias de índices, entrega los índices existentes:

SELECT
  indexname,
  indexdef
FROM pg_indexes
WHERE schemaname = 'public'
  AND tablename = 'orders'
ORDER BY indexname;

Paso 4: Eliminar N+1

N+1 ocurre cuando cargas una lista y luego consultas datos relacionados para cada fila. Añade trabajo de base de datos y espera de red.

// Mal: una consulta de customer por cada 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 ?? "" });
}
// Mejor: traer solo las columnas que necesita la 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,
      },
    },
  },
});

Pide a Claude Code mantener la forma de la respuesta y reducir el número de consultas. Así tendrá que leer la UI o el serializador, no solo agregar include de forma amplia.

Paso 5: Cambiar OFFSET profundo por keyset pagination

OFFSET 10000 LIMIT 50 suele ser lento porque la base todavía recorre las filas saltadas. Para feeds cronológicos o scroll infinito, usa como cursor el último created_at y 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;

El índice (status, created_at DESC, id DESC) encaja con este acceso. El id estabiliza el orden cuando varias filas tienen el mismo timestamp.

Paso 6: Sacar agregaciones de requests críticos

Los dashboards se vuelven lentos cuando cada request recalcula los mismos totales. Si el negocio acepta retraso, usa una tabla resumen o un refresh programado.

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();

Indica a Claude Code la frescura permitida: un minuto, cinco minutos, diario o estrictamente en tiempo real. Sin esa restricción, una optimización puede cambiar el significado del dashboard.

Presupuesto de consultas y pruebas

La mejora puede degradarse con cambios futuros. Un snapshot exacto del plan suele ser frágil; empieza por número de consultas, compatibilidad de respuesta y 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 del flujo

flowchart LR
  A["Log de consultas lentas"] --> B["EXPLAIN / EXPLAIN ANALYZE"]
  B --> C["Cambios de índice y SQL"]
  C --> D["N+1, paginación, agregación"]
  D --> E["Pruebas de presupuesto SQL"]
  E --> F["Despliegue y monitoreo"]

Errores frecuentes

  • Leer columnas grandes con SELECT * en páginas de listado.
  • Esperar demasiado de un índice solo sobre una columna de baja cardinalidad como status.
  • Intentar acelerar LIKE '%keyword%' con un índice B-tree normal.
  • Juzgar planes con datos pequeños de desarrollo.
  • Ejecutar migraciones de índice en hora pico sin revisar locks, disco y lag de réplicas.
  • Usar include del ORM y devolver todo el grafo de relaciones.

Checklist de despliegue

1. Endpoint y pantalla identificados.
2. SQL lento capturado desde logs o pg_stat_statements.
3. EXPLAIN antes/después guardado.
4. Índices existentes revisados.
5. N+1, OFFSET, columnas seleccionadas y agregaciones síncronas revisadas.
6. Compatibilidad de respuesta probada.
7. Prueba de número de consultas o latencia añadida.
8. Riesgo de locks, duración y rollback de índices documentados.
9. Métricas posteriores al release definidas.

La optimización SQL es un flujo de trabajo, no un truco aislado. Claude Code puede conectar código, migraciones y pruebas, pero la decisión final debe basarse en datos reales, planes de ejecución y requisitos del producto.

ClaudeCodeLab ofrece formación y consultoría basadas en este tipo de revisión práctica. Con un endpoint lento, logs y schema, puedes convertir una investigación puntual en una checklist SQL reutilizable para tu equipo.

#Claude Code #SQL #base de datos #rendimiento #optimización
Gratis

PDF gratis: cheatsheet de Claude Code

Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.

Cuidamos tus datos y no enviamos spam.

Masa

Sobre el autor

Masa

Ingeniero enfocado en workflows prácticos con Claude Code.