Pedro Fonseca
Omnes enim Christus, nihil sine Maria

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?

Ou seja,


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?

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.