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

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

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

Este post va directo al grano: errores que veo repetidamente en código y bases de datos de producción, por qué son peligrosos y cómo arreglarlos con ejemplos prácticos. Si gestionas consultas, esquemas o rendimiento, aplícalo hoy mismo.

1) Usar SELECT * en producción

Problema: retornas más columnas de las que necesitas, aumentas I/O, ancho de banda y memoria, y rompes el contrato de la API cuando cambian columnas.

-- Malo
SELECT * FROM users WHERE active = true LIMIT 100;

-- Mejor (selecciona solo lo necesario)
SELECT id, email, created_at FROM users WHERE active = true ORDER BY created_at DESC LIMIT 100;

Por qué importa: menos datos transferidos = consultas más rápidas y menos latencia. Además, evita fugas de datos sensibles.

2) Falta de índices o índices mal definidos

Problema: tablas grandes sin índices adecuados provocan full table scans; índices mal diseñados no se usan.

-- Consulta lenta
SELECT * FROM orders WHERE user_id = 123 AND created_at > now() - interval '30 days';

-- Crea un índice compuesto para esa consulta
CREATE INDEX ix_orders_user_created ON orders (user_id, created_at DESC);

-- Verifica con EXPLAIN ANALYZE (Postgres)
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 123 AND created_at > now() - interval '30 days';

Cómo detectar: EXPLAIN/EXPLAIN ANALYZE, monitoreo (slow query log, pg_stat_statements), y profiler de la app. Evita índices inútiles: demasiados índices ralentizan INSERT/UPDATE/DELETE.

3) Patrón N+1 desde la capa de aplicación

Problema: por cada fila haces otra consulta; escalará linealmente y colapsará bajo carga.

-- Código pseudo-problemático (Python/ORM)
for order in orders:
    user = db.query(User).filter(User.id == order.user_id).one()
    # N queries para N orders

-- Solución: precarga con JOIN o IN
user_ids = [o.user_id for o in orders]
users = db.query(User).filter(User.id.in_(user_ids)).all()
users_by_id = {u.id: u for u in users}
for order in orders:
    user = users_by_id[order.user_id]

Alternativa: usa JOIN o la funcionalidad de "eager loading" del ORM (e.g., select_related, prefetch_related).

4) Transacciones mal gestionadas

Problema: transacciones demasiado largas o sin commit/rollback, causan locks, bloquen replicación y pueden inflar WAL.

-- Malo: transacción abierta por mucho tiempo
BEGIN;
-- lógica de negocio lenta (HTTP call, cálculo pesado)
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- sin COMMIT por varios segundos o minutos

-- Mejor: minimizar la sección transaccional
-- haz llamadas externas antes y luego solo la parte crítica en la transacción
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
INSERT INTO ledger (...) VALUES (...);
COMMIT;

También: elige el nivel de aislamiento correcto y, cuando necesites consistencia para una fila, considera SELECT ... FOR UPDATE en lugar de bloquear más de lo necesario.

5) Concatenar strings y permitir SQL injection

Problema: construir consultas con interpolación de cadenas permite inyección SQL y compromete datos.

-- Vulnerable
query = "SELECT * FROM users WHERE email = '" + email + "'";

-- Seguro: parámetros/prepared statements
-- En Postgres (psycopg2)
cur.execute('SELECT * FROM users WHERE email = %s', (email,))

Siempre usa consultas parametrizadas en la capa de acceso a datos. Activa WAF/IDS si estás expuesto, pero la defensa real es parametrizar.

6) Aplicar funciones en columnas indexadas (rompe el uso del índice)

Problema: si haces WHERE LOWER(name) = 'ana' y el índice está sobre name, la base no usará el índice por defecto.

-- Malo (no usa índice en Postgres)
SELECT * FROM customers WHERE LOWER(name) = 'ana';

-- Soluciones:
-- 1) crear un índice funcional
CREATE INDEX ix_customers_lower_name ON customers (LOWER(name));

-- 2) almacenar una columna normalizada y consultar sobre ella
ALTER TABLE customers ADD COLUMN name_lower TEXT;
UPDATE customers SET name_lower = LOWER(name);
CREATE INDEX ix_customers_name_lower ON customers (name_lower);
-- Mantén la columna actualizada via trigger o en la app

Regla: evita transformar la columna en el WHERE; si necesitas, usa índices funcionales o columnas derivadas.

7) Tipos de datos incorrectos y falta de constraints

Problema: campos de tipo texto para fechas/facturas/monedas, sin CHECKs ni FK, llevan a inconsistencias y a costosas transformaciones en tiempo de consulta.

-- Malo
CREATE TABLE invoices (
  id SERIAL PRIMARY KEY,
  amount FLOAT,
  issued_date TEXT
);

-- Mejor
CREATE TABLE invoices (
  id SERIAL PRIMARY KEY,
  amount NUMERIC(12,2) NOT NULL CHECK (amount >= 0),
  issued_date DATE NOT NULL,
  customer_id INT NOT NULL REFERENCES customers(id)
);

Usa tipos adecuados: NUMERIC/DECIMAL para dinero, DATE/TIMESTAMP para fechas, JSONB para JSON en Postgres. Añade NOT NULL, CHECK y FOREIGN KEYS para mantener integridad.

Cómo detectar estos problemas en tu base

  • Revisa el slow query log y usa EXPLAIN ANALYZE regularmente.
  • Habilita y consulta herramientas como pg_stat_statements (Postgres) o Performance Schema (MySQL).
  • Audita el acceso a la BD desde la app: busca concatenaciones de strings y patrones N+1 en el código.

Checklist rápido para auditorías

  1. Sustituir SELECT * en endpoints públicos.
  2. Verificar índices para consultas frecuentes (EXPLAIN).
  3. Detectar y resolver N+1 en lógica de negocio.
  4. Revisar transacciones y acortar su duración.
  5. Eliminar concatenaciones SQL y usar prepared statements.
  6. Crear índices funcionales si aplicas funciones en WHERE.
  7. Corregir tipos de datos y añadir constraints.

Consejo avanzado: instala monitoreo de consultas (pg_stat_statements + pganalyze o similares), automatiza pruebas de carga para endpoints críticos y añade reglas de CI que ejecuten un set de EXPLAINs básicos contra migraciones/queries nuevas. Si no lo haces, incluso pequeños cambios pueden degradar tu base de datos en producción.

Advertencia: cambia índices y esquemas en ventanas de mantenimiento y verifica el impacto en INSERT/UPDATE/DELETE. El siguiente paso por tu cuenta: corre EXPLAIN ANALYZE en tus 10 queries más lentas y aplica las correcciones aquí descritas en una rama de pruebas.

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