18 de setembro de 2025
Última atualização em 21 de setembro de 2025
select_related e prefetch_related no Django
Cenário: Modelos da NFL
Imagine que temos os seguintes modelos:
from django.db import models
class Conference(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Team(models.Model):
name = models.CharField(max_length=100)
city = models.CharField(max_length=100)
conference = models.ForeignKey(Conference, on_delete=models.CASCADE)
def __str__(self):
return f"{self.city} {self.name}"
class Player(models.Model):
name = models.CharField(max_length=100)
position = models.CharField(max_length=10)
team = models.ForeignKey(Team, on_delete=models.CASCADE, related_name="players")
def __str__(self):
return self.name
Problema: Consultas N+1
Ao buscar jogadores e suas equipes, podemos enfrentar o problema de consultas N+1:
players = Player.objects.all()
print(players.query)
"""
SELECT "player"."id",
"player"."name",
"player"."position",
"player"."team_id"
FROM "player";
"""
for player in players:
print(f"{player.name} - {player.team.name}")
Repare que eu tento acessar player.team.name dentro do loop.
Isso resulta em uma consulta para buscar todos os jogadores e, em seguida, uma consulta adicional para cada jogador para buscar sua equipe.
Qual é o real problema e por que chamamos de consulta N+1?
- Quando se tenta acessar o player.team em um loop, o Django realiza uma nova consulta para cada jogador para buscar a equipe associada.
Ou seja,
- Se tivermos 10 jogadores
- teremos 1 consulta para buscar todos os jogadores
- 10 consultas para buscar as equipes
- resultando em 11 consultas no total.
Usando select_related
select_related é usado para relações ForeignKey e OneToOneField. Ele realiza um JOIN SQL para buscar os dados relacionados em uma única consulta.
players = Player.objects.select_related('team').all()
resultando em:
SELECT
"player"."id",
"player"."name",
"player"."position",
"player"."team_id",
"team"."id",
"team"."name",
"team"."city",
"team"."conference_id"
FROM "player"
INNER JOIN "team" ON "player"."team_id" = "team"."id";
Não é bala de prata
Em vez de N+1 consultas, agora temos apenas 1 consulta.
Apesar de realizar uma única consulta, é importante adicionar que dependendo do número de colunas e do tamanho dos dados, a consulta pode se tornar mais pesada, e consequentemente consumindo mais memória. Como isso pode acontecer?
- Imagine que temos 1000 jogadores.
- Cada time tem uma coluna de logo (imagem base64).
O Django trará os 1000 jogadores e todos os logos dos times de uma vez. Se cada logo tiver 50KB, isso resultará em 50MB de dados só para os logos. Isso pode ser um problema de performance e consumo de memória.