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

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

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

Trabajar con bases de datos relacionales parece sencillo hasta que una consulta lenta, un deadlock o una inyección arruinan tu día. Aquí tienes 7 errores comunes en SQL con porqués, cómo detectarlos y soluciones prácticas con ejemplos. Ve al grano: código, explicaciones claras y comandos para comprobar en producción.

1) Usar SELECT * en producción

Por qué ocurre: comodidad. SELECT * trae todas las columnas, muchas veces innecesarias, aumentando ancho de banda, I/O y memoria. Además rompe cuando cambias esquema.

Detectar: revisa consultas que transfieren muchas columnas, logs de consultas lentas o herramientas APM.

Solución: seleccionar solo las columnas necesarias y documentar el contrato de la consulta.

-- Evitar esto
SELECT * FROM orders WHERE customer_id = 42;

-- Mejor
SELECT id, total_amount, created_at FROM orders WHERE customer_id = 42;

2) No usar índices o usar índices incorrectos

Por qué ocurre: diseños rápidos, desconocimiento de cómo funcionan los índices o su mantenimiento (inserciones/updates más lentos si abusas).

Detectar: EXPLAIN/EXPLAIN ANALYZE muestra full table scans; tiempos altos en índices inexistentes.

Solución: crear índices adecuados y usar índices compuestos cuando las consultas filtran por varias columnas.

-- Consulta lenta: filtro por customer_id y created_at
SELECT id, total_amount FROM orders WHERE customer_id = 42 AND created_at > '2025-01-01';

-- Crear índice compuesto para cubrir el WHERE
CREATE INDEX idx_orders_customer_created_at ON orders (customer_id, created_at);

-- Para PostgreSQL, usar EXPLAIN ANALYZE
EXPLAIN ANALYZE SELECT id, total_amount FROM orders WHERE customer_id = 42 AND created_at > '2025-01-01';

Nota: el orden de columnas en índices compuestos importa: coloca primero las columnas más selectivas o las usadas en igualdad.

3) Aplicar funciones en columnas en filtros (rompe índices)

Por qué ocurre: para normalizar o comparar (ej. LOWER(name) = 'ana') y no se crea índice funcional.

Detectar: EXPLAIN muestra que el índice no es utilizado.

Soluciones:

  • Crear índices funcionales (Postgres) o columnas persistidas y indexarlas (MySQL).
  • O evitar la función: almacenar normalizado o buscar con collation apropiada.
-- Evitar esto (no usa índice en muchas DBs)
SELECT * FROM users WHERE LOWER(email) = 'juan@ejemplo.com';

-- Postgres: crear índice funcional
CREATE INDEX idx_users_lower_email ON users (LOWER(email));

-- O almacenar una columna email_normalized y indexarla
ALTER TABLE users ADD COLUMN email_normalized TEXT;
UPDATE users SET email_normalized = LOWER(email);
CREATE INDEX idx_users_email_normalized ON users (email_normalized);

4) No usar consultas parametrizadas / concatenar strings (riesgo de SQL injection)

Por qué ocurre: rapidez en pruebas, malas prácticas con ORMs o construir queries dinámicas sin escapar.

Detectar: búsqueda en el código por concatenaciones de SQL o logs que muestren queries con valores interpolados.

Solución: usar siempre prepared statements o binding de parámetros.

-- PELIGRO: concatenación directa
sql = "SELECT * FROM users WHERE username = '" + username + "'";

-- CORRECTO (pseudocódigo)
stmt = db.prepare("SELECT * FROM users WHERE username = ?");
stmt.execute([username]);

5) Long-running transactions y bloqueo excesivo

Por qué ocurre: lógica que mantiene transacción abierta mientras hace llamadas externas, o batching inadecuado.

Detectar: queries en pg_stat_activity (Postgres), INFORMATION_SCHEMA.PROCESSLIST (MySQL) con alta duración, aumento de deadlocks.

Soluciones:

  • Reducir el trabajo dentro de la transacción: preparar datos antes, luego abrir transacción solo para writes necesarios.
  • Usar niveles de aislamiento adecuados (por ejemplo, READ COMMITTED vs SERIALIZABLE) según necesidad.
  • Hacer commits frecuentes en procesos de batch para evitar retener locks por mucho tiempo.
-- Ejemplo: mal (transacción abierta durante llamadas externas)
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- Llamada HTTP externa que retrasa el commit
CALL external_service();
COMMIT;

-- Mejor: hacer la llamada externa antes y mantener la transacción corta
CALL external_service();
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;

6) Ignorar los tipos de datos y nulabilidad

Por qué ocurre: compatibilidades rápidas, usar tipos genéricos (VARCHAR, TEXT) o flags NULL mal pensadas.

Detectar: columnas con tipos inconsistentes, conversiones frecuentes, uso excesivo de CAST en consultas.

Soluciones:

  • Elegir tipos lo más específicos posible (INTEGER, TIMESTAMP WITH TIME ZONE, DECIMAL(10,2)).
  • Definir NOT NULL cuando aplique y usar defaults razonables.
-- Mejor definir precisión en montos
-- Evitar FLOAT para dinero
CREATE TABLE invoices (
  id SERIAL PRIMARY KEY,
  amount DECIMAL(12,2) NOT NULL,
  issued_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);

7) Confiar ciegamente en el ORM sin entender las queries que genera

Por qué ocurre: productividad del ORM, pero a veces genera N+1 queries, JOINS ineficientes o SELECTs con demasiadas columnas.

Detectar: profiler/SQL logs del ORM, consultas repetidas por petición (N+1).

Soluciones:

  • Revisa las queries generadas en staging/prod con muestra de tráfico real.
  • Usa herramientas del ORM para eager loading correctamente y evita lazy loads por defecto.
  • Cuando sea necesario, escribir consultas SQL optimizadas (views, materialized views o consultas RAW).
-- Ejemplo N+1 (pseudo-ORM)
for order in orders:
  print(order.customer.name)  -- genera una query por cada order

-- Solución: eager load
orders = Order.query.options(joinedload('customer')).all()

Comprobaciones prácticas rápidas

  • Usa EXPLAIN / EXPLAIN ANALYZE para entender planes de ejecución.
  • Habilita logs de consultas lentas (slow query log) y revisa con frecuencia.
  • Monitorea métricas: latencia, I/O, locks, tasa de cache hits (buffer cache).

Comandos útiles (Postgres):

-- Plan de ejecución y tiempos
EXPLAIN ANALYZE SELECT ...;

-- Consultas más costosas (requiere pg_stat_statements)
SELECT query, total_time, calls FROM pg_stat_statements ORDER BY total_time DESC LIMIT 10;

-- Consultas activas y locks
SELECT pid, now() - pg_stat_activity.query_start AS duration, query FROM pg_stat_activity WHERE state <> 'idle' ORDER BY duration DESC;

Último consejo avanzado: establece una práctica de revisión regular de SQL en tu equipo: lee planes de ejecución en code reviews, añade pruebas de performance para consultas críticas y automatiza alertas para regressiones en latencia. Evita arreglos rápidos que no documentes: lo barato a corto plazo suele costar mucho en producción.

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