9 errores críticos que todos cometen con SQL (y cómo solucionarlos)
Este artículo va directo al grano: errores reales, cómo detectarlos y soluciones prácticas con ejemplos. Aplícalos hoy en tu base de datos relacional (Postgres, MySQL, SQL Server, etc.).
1. Usar SELECT * en producción
Qué pasa: Recuperas más columnas de las necesarias, aumentas I/O, ancho de banda y acoplas tu aplicación a cambios de esquema.
Cómo detectarlo: Revisar queries lentas o logs de consulta; buscar "SELECT *" en código y procedimientos almacenados.
Solución: Selecciona explícitamente las columnas que necesitas. Mantén DTOs/Views claros y versiona cambios de esquema.
-- Malo
SELECT * FROM orders WHERE customer_id = 42;
-- Bueno
SELECT id, total_amount, created_at FROM orders WHERE customer_id = 42;
2. Índices incorrectos o inexistentes
Qué pasa: Scans completos de tabla, alta latencia en lecturas y bloqueos en escrituras.
Cómo detectarlo: Usa EXPLAIN/EXPLAIN ANALYZE; observa secuencias (seq scan) o tablas grandes con high cost en planes.
Solución: Añade índices a columnas usadas en WHERE, JOIN y ORDER BY; evita índices innecesarios en columnas con alta cardinalidad baja (booleans) y reevalúa índices compuestos.
-- Índice simple
CREATE INDEX idx_orders_customer ON orders(customer_id);
-- Índice compuesto para consultas que filtran por customer y ordenan por fecha
CREATE INDEX idx_orders_customer_date ON orders(customer_id, created_at DESC);
3. No usar transacciones cuando corresponde
Qué pasa: Estados inconsistentes, filas huérfanas, condiciones de carrera en operaciones multi-step.
Cómo detectarlo: Revisa operaciones que modifican múltiples tablas; errores intermitentes en producción tras caídas o retries.
Solución: Encierra operaciones que deben ser atómicas en transacciones y maneja rollback en errores.
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
-- En caso de error: ROLLBACK;
4. Olvidar WHERE en UPDATE/DELETE
Qué pasa: Se actualizan o eliminan todas las filas por accidente.
Cómo detectarlo: Revisa logs, auditoría; pruebas unitarias que cubran mutaciones masivas.
Solución: Siempre prueba la cláusula WHERE con SELECT antes de ejecutar la mutación; usa transacciones y límites si aplica; habilita soft-delete o auditoría.
-- Peligroso
DELETE FROM users;
-- Seguro: validar con SELECT, luego eliminar
SELECT id FROM users WHERE last_login < '2020-01-01';
DELETE FROM users WHERE last_login < '2020-01-01';
5. Ignorar EXPLAIN y no perfilar consultas
Qué pasa: Optimización por intuición en lugar de datos; cambios que rompen rendimiento en producción.
Cómo detectarlo: Métricas de latencia crecientes y queries que escalan mal con el volumen de datos.
Solución: Haz EXPLAIN/EXPLAIN ANALYZE antes y después de cambiar índices o queries. Automatiza profiling en staging.
EXPLAIN ANALYZE
SELECT id, total_amount FROM orders WHERE customer_id = 42 ORDER BY created_at DESC LIMIT 10;
6. Manejo incorrecto de NULLs
Qué pasa: Comparaciones inesperadas, joins que omiten filas y conteos erróneos.
Cómo detectarlo: Resultados inconsistentes; diferencias entre COUNT(col) y COUNT(*).
Solución: Diseña columnas con NOT NULL cuando aplique; usa COALESCE en cálculos; comprende tri-logic booleana (TRUE/FALSE/UNKNOWN).
-- Evitar sorpresa en agregaciones
SELECT COUNT(*) FROM table; -- cuenta todas las filas
SELECT COUNT(column) FROM table; -- cuenta solo donde column IS NOT NULL
-- Manejar NULLs
SELECT COALESCE(discount, 0) FROM orders;
7. Esquema mal normalizado o sobre-normalizado
Qué pasa: Demasiadas joins (over-normalized) o redundancia y anomalías (under-normalized).
Cómo detectarlo: Consultas complejas con muchas joins; problemas de integridad o duplicación.
Solución: Sigue 3NF como punto de partida; normaliza para integridad y desnormaliza selectivamente para rendimiento, con triggers o procesos ETL para mantener consistencia.
8. N+1 queries (especialmente con ORMs)
Qué pasa: Para cada fila recuperas datos adicionales en queries separados; latencia explosiva.
Cómo detectarlo: Revisa logs de SQL durante rendering de páginas o endpoints; QPS aumenta con nº de filas.
Solución: Usa JOINs, subqueries o técnicas de eager loading del ORM (JOIN FETCH, includes, prefetch_related, etc.).
-- N+1 patrón (pseudo)
for order in orders:
SELECT * FROM order_items WHERE order_id = order.id; -- una query por order
-- Solución: traer todo en una sola consulta
SELECT o.id, oi.* FROM orders o LEFT JOIN order_items oi ON oi.order_id = o.id WHERE o.customer_id = 42;
9. No gestionar permisos y exposición de datos
Qué pasa: Aplicaciones que exponen columnas sensibles o permiten consultas malintencionadas por permisos laxos.
Cómo detectarlo: Auditorías de seguridad, revisiones de roles y privilegios, pruebas de penetración.
Solución: Aplica el principio de menor privilegio en DB roles; usa vistas o columnas enmascaradas para exponer solo lo necesario; valida entrada y evita SQL dinámico sin parametrización.
-- Evitar SQL dinámico inseguro
-- Malo
EXECUTE 'SELECT * FROM users WHERE username = "' || input || '"';
-- Bueno: parámetros preparados (ejemplo en pseudo-API)
PREPARE q(text) AS SELECT * FROM users WHERE username = $1;
EXECUTE q('alice');
Aplicando estas correcciones evitarás la mayoría de incidentes operativos y de rendimiento con bases de datos relacionales. Como siguiente paso avanzado: automatiza checks con linters SQL, agrega alertas de queries lentas y crea pruebas de rendimiento que corran en CI. Precaución: los cambios de índices y esquemas en producción requieren pruebas y migraciones planeadas para evitar bloqueos prolongados.
¿Quieres comentar?
Inicia sesión con Telegram para participar en la conversación