El problema N+1 aparece cuando hacés 1 consulta para traer una lista y luego N consultas adicionales, una por cada elemento, para cargar una relación. Con 20 pedidos no se nota; con 5000 son 5001 viajes a la base, y la latencia de red domina.
Típico con ORMs y carga perezosa (lazy loading): Código: python
pedidos = Pedido.objects.all() # 1 query
for p in pedidos:
print(p.cliente.nombre) # 1 query por pedido
Cómo lo detecto:
Activar el log de SQL en desarrollo o usar herramientas como Django Debug Toolbar, Bullet (Rails) o las estadísticas de Hibernate.
En producción, el APM muestra muchas consultas idénticas con distinto parámetro dentro de un mismo request.
Un test que verifique la cantidad de queries (por ejemplo assertNumQueries en Django) evita regresiones.
Cómo lo resuelvo:
Eager loading con JOIN para relaciones a uno: select_related en Django, JOIN FETCH en JPA, includes en Rails.
Carga por lotes con IN para relaciones a muchos: prefetch_related, @BatchSize, o el patrón DataLoader en GraphQL.
Traer solo las columnas necesarias (proyecciones/DTOs) y paginar el listado.
Verificar que la clave foránea tenga índice.
Código: text
pedidos = Pedido.objects.select_related('cliente')[:50] # 1 query con JOIN
Ojo: eager loading indiscriminado también daña, porque un JOIN con varias colecciones puede multiplicar filas (producto cartesiano). Hay que elegir la estrategia por relación y medir antes y después.
Errores comunes
Proponer cachear todo sin atacar la causa. No saber cómo detectarlo más allá de 'está lento'. Cargar eager todas las relaciones y generar JOINs enormes.
Redactado con el asistente Underc0de IA. Actualizado el 11/11/2026. ¿Ves un error? Contanos en el foro.