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, 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.
Usando prefetch_related
prefetch_related é indicado para relações ManyToManyField e reversas de ForeignKey (ex: acessar todos os jogadores de um time). Em vez de um JOIN, ele executa consultas separadas e faz o cruzamento em memória pelo Python.
teams = Team.objects.prefetch_related('players').all()
for team in teams:
for player in team.players.all():
print(f"{player.name} - {team.name}")
Isso resulta em exatamente 2 consultas:
SELECT "team"."id", "team"."name", "team"."city", "team"."conference_id"
FROM "team";
SELECT "player"."id", "player"."name", "player"."position", "player"."team_id"
FROM "player"
WHERE "player"."team_id" IN (1, 2, 3, ...);
O Django então mapeia os jogadores para seus respectivos times em memória, sem precisar de mais queries dentro do loop.
Por que não usar select_related aqui?
select_related usa JOIN — isso seria problemático em relações ManyToMany ou reversas de FK, porque duplicaria linhas no resultado (um time com 10 jogadores geraria 10 linhas repetidas do time). O prefetch_related evita isso fazendo as consultas separadas.
Combinando os dois
É perfeitamente válido usar os dois juntos. Por exemplo, buscar jogadores com seu time e a conferência do time:
players = Player.objects.select_related('team__conference').all()
Ou buscar times com seus jogadores e a conferência do time:
teams = Team.objects.select_related('conference').prefetch_related('players').all()
Quando usar cada um?
| Situação | Use |
|---|---|
| ForeignKey / OneToOneField (lado "filho") | select_related |
| Relação reversa de FK (related_name) | prefetch_related |
| ManyToManyField | prefetch_related |
| Dados pesados (imagens, textos longos) | Avalie o custo do JOIN |
A regra prática: se você vai acessar um único objeto relacionado por registro, use select_related. Se vai iterar sobre uma coleção de relacionados, use prefetch_related.