Django · PostgreSQL · Python

Pedro Fonseca · · atualizado em 21 de setembro de 2025 · 6 min de leitura

Neste artigo

select_related e prefetch_related no Django

Cenário: Modelos da NFL

Imagine que temos os seguintes modelos:

python
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:

python
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.

select_related é usado para relações ForeignKey e OneToOneField. Ele realiza um JOIN SQL para buscar os dados relacionados em uma única consulta.

python
players = Player.objects.select_related('team').all()

resultando em:

sql
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.


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.

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:

sql
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.

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:

python
players = Player.objects.select_related('team__conference').all()

Ou buscar times com seus jogadores e a conferência do time:

python
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.

Continue lendo

Sobre o autor

Pedro Fonseca

Engenheiro de software especializado em backend, Python e sistemas distribuídos.