5 errores críticos que todos cometen con SQL (y cómo solucionarlos)

sql 5 errores críticos que todos cometen con SQL (y cómo solucionarlos)

5 errores críticos que todos cometen con SQL (y cómo solucionarlos)

Voy al grano: estos errores aparecen en proyectos pequeños y grandes. Detectarlos y arreglarlos eleva rendimiento, seguridad y mantenimiento. Aquí tienes ejemplos concretos, cómo detectarlos y correcciones prácticas.

1) Confiar en scans de tabla: ausencia o mal uso de índices

Qué pasa: consultas frecuentes realizan full table scans, causando latencia y carga alta. Suele ocurrir por columnas sin índice, usar funciones sobre columnas (bloquea el índice) o esperar que el motor «adivine» el mejor plan.

Ejemplo problemático:

SELECT * FROM orders WHERE customer_id = 123;

Si customer_id no está indexado, cada consulta recorre toda la tabla.

Solución:

-- PostgreSQL
CREATE INDEX CONCURRENTLY idx_orders_customer_id ON orders(customer_id);

-- MySQL
CREATE INDEX idx_orders_customer_id ON orders(customer_id);

Evita esto: WHERE LOWER(email) = 'x' — una función sobre la columna evita el uso del índice. En PostgreSQL, usa un índice de expresión:

CREATE INDEX idx_users_lower_email ON users (LOWER(email));
-- o mejor: normaliza emails a minúsculas al guardar

Cómo detectar: usar EXPLAIN ANALYZE (Postgres) o EXPLAIN (MySQL) y revisar planes; revisar logs de consultas lentas y herramientas como pg_stat_statements.

2) SELECT * y sobre-fetching

Qué pasa: pedir todas las columnas transmite datos innecesarios (cpu, io, red). Además impide que la consulta sea cubierta por un índice.

Ejemplo problemático:

SELECT * FROM users WHERE id = 42;

Solución: pide solo lo que necesitas; si necesitas datos que están en un índice, úsalo como índice cubriente.

SELECT id, email, name FROM users WHERE id = 42;
-- O, si solo necesitas email:
-- CREATE INDEX idx_users_email_id ON users (id, email);
-- SELECT email FROM users WHERE id = 42;

Cómo detectar: revisa payloads en endpoints, perfiles de red y EXPLAIN para ver si la consulta lee columnas innecesarias.

3) UPDATE/DELETE sin WHERE o joins mal construidos (productos del cartesian)

Qué pasa: comandos que alteran o borran más filas de las previstas. Causa downtime lógico, pérdida de datos y horas de depuración.

Ejemplo peligroso:

-- ¡Peligro!
UPDATE orders SET status = 'cancelled'; -- se aplica a toda la tabla

Mejor patrón: siempre prueba con una SELECT idéntica antes de ejecutar la modificación; envuelve en transacción; cuando sea posible, usa RETURNING para comprobar cambios (Postgres).

BEGIN;
SELECT id FROM orders WHERE id = 123; -- verificar
UPDATE orders SET status = 'cancelled' WHERE id = 123 RETURNING id, status;
COMMIT;

-- Si hay joins:
UPDATE o
SET status = 'cancelled'
FROM customers c
WHERE o.customer_id = c.id AND c.flag = true; -- revisar JOIN para evitar multiplicación

Cómo minimizar riesgo: usar transacciones, backups, LIMIT con ORDER BY en casos de correcciones iterativas, y pruebas en staging.

4) Construir consultas concatenando cadenas: inyección SQL

Qué pasa: concatenar parámetros directamente en la consulta permite inyección y compromete datos o integridad.

Ejemplo vulnerable (pseudocódigo):

// NO usar
const q = "SELECT * FROM users WHERE email = '" + email + "'";
db.query(q);

Corrección: usar consultas parametrizadas / prepared statements.

// Node + pg (Postgres)
const q = 'SELECT id, email FROM users WHERE email = $1';
const res = await client.query(q, [email]);

// Python + psycopg2
cur.execute('SELECT id FROM users WHERE email = %s', (email,));

Cómo detectar: revisa el código para concatenaciones de strings con inputs; ejecuta pruebas de seguridad (OWASP SQLi), escaneo dinámico y revisiones de PR que validen parámetros.

5) N+1 queries y falta de batching

Qué pasa: leer una lista y por cada fila ejecutar otra consulta produce latencia exponencial. Muy común en ORMs mal configurados.

Ejemplo N+1 (pseudocódigo):

-- Pseudocódigo: obtener posts y sus comentarios
posts = db.query('SELECT * FROM posts WHERE user_id = ?', [user_id]);
for (p of posts) {
  p.comments = db.query('SELECT * FROM comments WHERE post_id = ?', [p.id]);
}

Solución: hacer JOINs, usar IN (...) o aprovechar funciones de agregación/arrays para traer todo en una consulta.

-- Usando JOIN (devuelve duplicados por fila de comment, agrupa si se necesita)
SELECT p.id, p.title, c.id AS comment_id, c.body
FROM posts p
LEFT JOIN comments c ON c.post_id = p.id
WHERE p.user_id = 42;

-- O, resultados agregados en Postgres
SELECT p.id, p.title, COALESCE(json_agg(c.*) FILTER (WHERE c.id IS NOT NULL), '[]') AS comments
FROM posts p
LEFT JOIN comments c ON c.post_id = p.id
WHERE p.user_id = 42
GROUP BY p.id;

-- Alternativa con IN (batching)
SELECT * FROM comments WHERE post_id IN (1,2,3,4);

Cómo detectar: revisa logs para múltiples consultas similares por request; utiliza tracing (OpenTelemetry) y perfiles de ORM (Eloquent, ActiveRecord, Hibernate) que muestran N+1 warnings.

Herramientas rápidas para diagnosticar

  • EXPLAIN / EXPLAIN ANALYZE (Postgres), EXPLAIN (MySQL): revisa scans, joins y costos.
  • pg_stat_statements: identifica las consultas más costosas en Postgres.
  • Slow query log (MySQL): activa y revisa patrones repetidos.
  • Tracing / APM (Datadog, New Relic, OpenTelemetry): identificar latencias por request.

Checklist rápido para cada PR que toca SQL

  • ¿Se usaron parámetros en lugar de concatenar? (evitar inyección)
  • ¿Se seleccionan solo las columnas necesarias?
  • ¿La consulta usa índices o necesita un índice nuevo? (ver EXPLAIN)
  • ¿Hay riesgo de N+1? ¿Se puede hacer batching o un JOIN eficiente?
  • ¿UPDATE/DELETE tienen WHERE verificado y pruebas en staging?

Consejo avanzado: automatiza la detección básica en CI: ejecuta EXPLAIN en las queries más críticas (o los mappers/queries SQL de tu repo) y falla el pipeline si detecta full table scans repetidos sobre tablas grandes. También configura el slow query log y agrega alertas si el 95º percentil de latencia sube.

Siguiente paso: identifica las 10 consultas más frecuentes/nevralgicas en tu app (pg_stat_statements o slow log) y aplica EXPLAIN + pequeños cambios (index, SELECT explícito, batching). Repite y automatiza la verificación en tu flujo de desarrollo.

Comentarios
¿Quieres comentar?

Inicia sesión con Telegram para participar en la conversación


Comentarios (0)

Aún no hay comentarios. ¡Sé el primero en comentar!

Iniciar Sesión