5 mejores prácticas para escribir consultas SQL eficientes y seguras

sql 5 mejores prácticas para escribir consultas SQL eficientes y seguras

5 mejores prácticas para escribir consultas SQL eficientes y seguras

Si trabajas con bases de datos relacionales a diario, hay una diferencia entre consultas que funcionan y consultas que escalan. Aquí tienes prácticas concretas, ejemplos y por qué aplicarlas en tus proyectos (ejemplos orientados a PostgreSQL, pero aplicables a la mayoría de motores).

1) Selecciona solo las columnas que necesitas

Evita SELECT *. Traer columnas innecesarias impacta en I/O, memoria y red, además de evitar el uso de índices cubriendo.

-- MAL
SELECT * FROM orders WHERE customer_id = 42;

-- BIEN
SELECT id, order_date, total_amount FROM orders WHERE customer_id = 42;

Por qué: al limitar columnas, el planner puede usar índices cubiertos (index-only scans) y reduces el tiempo de serialización.

2) Índices: diseño y uso correcto

  • No indexar todo: indexar columnas que se usan en WHERE, JOIN y ORDER BY.
  • Evita funciones sobre columnas indexadas (ej. LOWER(col)) sin un índice funcional.
  • Considera índices parciales o por expresión para patrones específicos.
-- Evitar: función sobre columna impide uso de índice simple
WHERE LOWER(email) = 'a@b.com'

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

-- Índice parcial para estado activo
CREATE INDEX idx_orders_active ON orders(order_date) WHERE status = 'active';

Por qué: los índices bien pensados reducen lecturas; los parciales son útiles para columnas con cardinalidad sesgada.

3) JOINs y filtros: orden y cardinalidad importan

Aplica filtros temprano y evita operaciones que materialicen demasiadas filas antes de filtrar.

-- MAL: traer muchas filas y luego filtrar en aplicación
SELECT o.id, c.name
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE c.country = 'US' AND o.total_amount > 100;

-- BIEN: usar índices en ambas condiciones y filtrar por columnas selectivas
-- Asegúrate de tener índices en customers(country) y orders(customer_id, total_amount)

Por qué: el planner estima costos por cardinalidad; mantener estadísticas actualizadas (ANALYZE) es clave.

4) Pagination correcta: evita OFFSET para grandes desplazamientos

OFFSET N es sencillo pero desperdicia trabajo al descartar filas anteriores. Usa paginación por clave (keyset pagination).

-- MAL (lentísimo con OFFSET grande)
SELECT id, created_at FROM posts ORDER BY created_at DESC LIMIT 20 OFFSET 10000;

-- BIEN (keyset pagination)
-- Suponiendo que la última fila mostrada tiene created_at = '2026-01-01' y id = 123
SELECT id, created_at FROM posts
WHERE (created_at < '2026-01-01')
   OR (created_at = '2026-01-01' AND id < 123)
ORDER BY created_at DESC, id DESC
LIMIT 20;

Por qué: keyset usa índices y evita escanear/descartar N filas.

5) Mide: EXPLAIN, EXPLAIN ANALYZE y métricas

No adivines: inspecciona planes de ejecución y tiempos reales.

-- Ver plan estimado
EXPLAIN SELECT ...;

-- Ejecuta con tiempos y filas reales (postgreSQL)
EXPLAIN ANALYZE SELECT ...;

Busca: secuencias de escaneo costosas (seq scan), joins cartesianos inesperados, alta escritura de temporales (disk). Si ves sec scans en columnas selectivas, revisa estadísticas o añade índice.

Seguridad y consistencia: prácticas que no fallan

  • Usa consultas parametrizadas / prepared statements para evitar SQL injection.
  • Define tipos de datos correctos y límites (VARCHAR(n), NUMERIC con precisión) para prevenir datos inválidos y reducir tamaño.
  • Maneja transacciones con claridad: COMMIT/ROLLBACK y el isolation level apropiado (por defecto READ COMMITTED en Postgres suele bastar; usa SERIALIZABLE solo cuando lo necesites).
-- Parámetros en aplicación (ejemplo conceptual)
psql.query('SELECT id, name FROM users WHERE email = $1', [email]);

Consejos prácticos y correcciones rápidas

  • Evita usar LIKE '%foo%': si necesitas búsquedas por subcadena, considera trigram indexes o full-text search.
  • CTEs: en PostgreSQL <=11, las CTEs son fences de optimización; en 12+ puedes controlar MATERIALIZED/NOT MATERIALIZED. Para consultas críticas, compara CTE vs subquery.
  • Mantén VACUUM/ANALYZE programado para bases OLTP; configuraciones por defecto no siempre son óptimas para cargas intensivas.
-- Evitar LIKE con wildcard inicial; usar trigram para búsqueda eficiente
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX idx_people_name_trgm ON people USING gin (name gin_trgm_ops);
SELECT id, name FROM people WHERE name ILIKE '%martínez%';

Errores frecuentes y cómo evitarlos

  • No actualizar estadísticas tras cambios masivos de datos → ejecutar ANALYZE.
  • Crear índices sin medir impacto en escrituras → evalúa costo de insert/update/delete.
  • Confiar en un solo ambiente para pruebas de rendimiento → replica producción o usa dataset representativo.

Por qué estas prácticas funcionan

Porque atacan las dos fuentes primarias de lentitud: I/O (lecturas/escrituras innecesarias) y planeamiento ineficiente (estimaciones erróneas). Reducir filas/columnas procesadas, permitir index usage y medir con EXPLAIN es la receta.

Próximo paso avanzado: automatiza pruebas de regresión de consultas (benchmarks reproducibles) y añade monitoreo de consultas lentas en producción. Advertencia: optimizar sin medir puede empeorar el rendimiento; siempre compara métricas antes y después.

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