Django ORM: otimize as consultas para o máximo desempenho
Guia completo de otimização de consultas no ORM do Django. select_related, prefetch_related, índices, análise do problema N+1 e técnicas avançadas para aplicações de alto desempenho.

O ORM do Django oferece uma abstração elegante para a interação com o banco de dados, mas essa simplicidade pode esconder problemas críticos de desempenho. Uma aplicação Django mal otimizada chega a gerar centenas de consultas onde uma única bastaria. Este guia explora as técnicas essenciais para identificar e resolver esses problemas.
Antes de otimizar, é preciso medir. O uso do django-debug-toolbar em desenvolvimento permite visualizar cada consulta SQL gerada e identificar rapidamente os gargalos.
Entender o problema N+1
O problema N+1 representa a armadilha mais comum em ORMs. Ele aparece quando uma consulta inicial recupera N objetos e, em seguida, são executadas N consultas adicionais para acessar as relações de cada objeto. Essa multiplicação de consultas degrada severamente o desempenho.
# models.py
from django.db import models
class Author(models.Model):
"""Modelo que representa o autor de um livro."""
name = models.CharField(max_length=200)
email = models.EmailField(unique=True)
bio = models.TextField(blank=True)
def __str__(self):
return self.name
class Book(models.Model):
"""Modelo que representa um livro com seu autor."""
title = models.CharField(max_length=300)
author = models.ForeignKey(
Author,
on_delete=models.CASCADE,
related_name='books'
)
published_date = models.DateField()
isbn = models.CharField(max_length=13, unique=True)
def __str__(self):
return self.titleEsses modelos simples permitem ilustrar o problema N+1 de forma concreta.
# views.py - Exemplo problemático
def list_books_bad(request):
"""❌ Esta view gera N+1 consultas."""
books = Book.objects.all() # 1 consulta para os livros
for book in books:
# Cada acesso a book.author dispara uma consulta SQL
print(f"{book.title} por {book.author.name}")
# Com 100 livros = 101 consultas SQL!
return render(request, 'books/list.html', {'books': books})Esse código parece inofensivo, mas gera uma consulta por livro para recuperar o autor associado.
Resolver o N+1 com select_related
O método select_related realiza um JOIN em SQL e recupera os dados relacionados em uma única consulta. Funciona para relações ForeignKey e OneToOneField.
# views.py - Solução otimizada com select_related
def list_books_optimized(request):
"""✅ Esta view gera uma única consulta com JOIN."""
# select_related realiza um JOIN SQL
books = Book.objects.select_related('author').all()
for book in books:
# Nenhuma consulta adicional: o autor já está carregado
print(f"{book.title} por {book.author.name}")
# Total: 1 única consulta SQL, não importa o número de livros
return render(request, 'books/list.html', {'books': books})A consulta SQL gerada utiliza um LEFT OUTER JOIN para recuperar os autores junto com os livros.
# Encadear select_related para relações aninhadas
# models.py
class Publisher(models.Model):
name = models.CharField(max_length=200)
country = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=300)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
# views.py
def list_books_with_details(request):
"""Recupera livros, autores e editoras em uma única consulta."""
books = Book.objects.select_related(
'author', # ForeignKey para Author
'publisher' # ForeignKey para Publisher
).all()
return render(request, 'books/list.html', {'books': books})Várias relações podem ser otimizadas ao mesmo tempo listando-as em select_related.
Otimizar relações ManyToMany com prefetch_related
Para relações ManyToMany ou relações reversas (ForeignKey vista do outro lado), o prefetch_related executa consultas separadas, porém otimizadas, evitando JOINs gigantes.
# models.py
class Tag(models.Model):
"""Tags para categorizar livros."""
name = models.CharField(max_length=50, unique=True)
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=300)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
tags = models.ManyToManyField(Tag, related_name='books')A relação ManyToMany entre Book e Tag exige prefetch_related para uma otimização eficaz.
# views.py - Otimização ManyToMany
def list_books_with_tags(request):
"""✅ Recupera os livros e suas tags com eficiência."""
books = Book.objects.prefetch_related('tags').all()
for book in books:
# As tags são pré-carregadas, sem consulta adicional
tag_names = [tag.name for tag in book.tags.all()]
print(f"{book.title}: {', '.join(tag_names)}")
# Total: 2 consultas (livros + tags), independentemente do volume
return render(request, 'books/list.html', {'books': books})O prefetch_related executa uma consulta separada para as tags e depois faz a junção em Python.
Utilize select_related para ForeignKey e OneToOne (JOIN SQL). Utilize prefetch_related para ManyToMany e relações reversas (consultas separadas). Ambos podem ser combinados em um mesmo QuerySet.
Personalizar o pré-carregamento com objetos Prefetch
O objeto Prefetch permite controle fino sobre os dados pré-carregados: filtragem, ordenação e até limitação do número de resultados.
# views.py
from django.db.models import Prefetch
def list_authors_with_recent_books(request):
"""Recupera os autores apenas com seus livros recentes."""
# Prefetch personalizado: somente livros a partir de 2025
recent_books_prefetch = Prefetch(
'books',
queryset=Book.objects.filter(
published_date__year__gte=2025
).order_by('-published_date'),
to_attr='recent_books' # Armazenado em um atributo personalizado
)
authors = Author.objects.prefetch_related(
recent_books_prefetch
).all()
for author in authors:
# Acesso pelo atributo personalizado
for book in author.recent_books:
print(f"{author.name}: {book.title}")
return render(request, 'authors/list.html', {'authors': authors})O atributo to_attr guarda os resultados em uma lista Python em vez do manager habitual.
# Combinação avançada: select_related + Prefetch
def list_authors_complete(request):
"""Exemplo completo de otimização em vários níveis."""
authors = Author.objects.prefetch_related(
Prefetch(
'books',
queryset=Book.objects.select_related(
'publisher' # Otimiza também a editora de cada livro
).prefetch_related(
'tags' # E as tags de cada livro
).filter(published_date__year=2026)
)
).all()
return render(request, 'authors/complete.html', {'authors': authors})Essa abordagem reduz drasticamente o número de consultas em estruturas de dados complexas.
Pronto para mandar bem nas entrevistas de Django?
Pratique com nossos simuladores interativos, flashcards e testes tecnicos.
Usar only() e defer() para limitar as colunas
Por padrão, o Django recupera todas as colunas da tabela. Em modelos com muitos campos ou campos grandes, limitar as colunas melhora o desempenho.
# views.py
def list_books_minimal(request):
"""Recupera apenas as colunas necessárias."""
# only() especifica as colunas a incluir
books = Book.objects.only(
'id',
'title',
'published_date'
).select_related('author')
# Atenção: acessar um campo não incluído dispara uma consulta
for book in books:
print(book.title) # OK, incluído
# print(book.isbn) # Geraria uma consulta adicional
return render(request, 'books/list.html', {'books': books})O método only() cria um objeto "deferred" que carrega somente as colunas indicadas.
# defer() para excluir colunas específicas
def list_authors_without_bio(request):
"""Exclui campos grandes raramente usados."""
# defer() exclui as colunas indicadas
authors = Author.objects.defer(
'bio' # O TextField não é carregado
).all()
for author in authors:
print(author.name) # OK
print(author.email) # OK
# author.bio carregaria o campo sob demanda
return render(request, 'authors/list.html', {'authors': authors})O defer() é o inverso do only(): as colunas listadas não são carregadas inicialmente.
Otimizar com values() e values_list()
Quando apenas alguns valores são necessários, sem objetos completos do modelo, values() e values_list() retornam dicionários ou tuplas mais leves.
# views.py
def get_book_titles(request):
"""Recupera apenas os títulos como uma lista."""
# values_list retorna tuplas
titles = Book.objects.values_list('title', flat=True)
# Resultado: ['Livro 1', 'Livro 2', ...]
# values retorna dicionários
book_data = Book.objects.values('title', 'published_date')
# Resultado: [{'title': 'Livro 1', 'published_date': ...}, ...]
return render(request, 'books/titles.html', {'titles': titles})Esses métodos evitam a instância de objetos do modelo, reduzindo o consumo de memória.
# Combinação com agregações
from django.db.models import Count, Avg
def get_author_statistics(request):
"""Estatísticas por autor sem carregar objetos completos."""
stats = Author.objects.values('name').annotate(
book_count=Count('books'),
avg_year=Avg('books__published_date__year')
).order_by('-book_count')
# Resultado: [{'name': 'Autor', 'book_count': 5, 'avg_year': 2024}, ...]
return render(request, 'authors/stats.html', {'stats': stats})A anotação permite executar cálculos diretamente no banco de dados.
Criar índices para acelerar as consultas
Índices no banco de dados aceleram drasticamente as buscas. O Django permite defini-los diretamente nos modelos.
# models.py
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=300, db_index=True)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
published_date = models.DateField()
isbn = models.CharField(max_length=13, unique=True)
status = models.CharField(max_length=20, default='available')
class Meta:
# Índices compostos para consultas frequentes
indexes = [
# Índice na data de publicação (ordenação frequente)
models.Index(
fields=['published_date'],
name='book_pub_date_idx'
),
# Índice composto para filtrar por autor + status
models.Index(
fields=['author', 'status'],
name='book_author_status_idx'
),
# Índice parcial: apenas livros disponíveis
models.Index(
fields=['published_date'],
name='book_available_idx',
condition=models.Q(status='available')
),
]
# Ordenação padrão que aproveita o índice
ordering = ['-published_date']Esses índices melhoram o desempenho das consultas que filtram por essas colunas.
Índices aceleram leituras, mas tornam as escritas (INSERT, UPDATE) ligeiramente mais lentas. Crie índices apenas em colunas usadas com frequência em cláusulas WHERE, ORDER BY ou JOIN.
Recorrer a consultas SQL brutas quando necessário
Para consultas complexas ou otimizações específicas do banco, consultas SQL brutas oferecem controle total.
# views.py
from django.db import connection
def get_books_with_raw_sql(request):
"""Consulta bruta para casos especiais."""
# Método 1: raw() para recuperar objetos do modelo
books = Book.objects.raw('''
SELECT b.*, a.name as author_name
FROM library_book b
INNER JOIN library_author a ON b.author_id = a.id
WHERE b.published_date > %s
ORDER BY b.published_date DESC
''', ['2025-01-01'])
return render(request, 'books/list.html', {'books': books})
def execute_custom_query(request):
"""Execução direta para consultas que não são SELECT."""
with connection.cursor() as cursor:
# Consulta com agregação complexa
cursor.execute('''
SELECT
a.name,
COUNT(b.id) as book_count,
AVG(EXTRACT(YEAR FROM b.published_date)) as avg_year
FROM library_author a
LEFT JOIN library_book b ON b.author_id = a.id
GROUP BY a.id, a.name
HAVING COUNT(b.id) > 2
ORDER BY book_count DESC
''')
results = cursor.fetchall()
return render(request, 'stats.html', {'results': results})Consultas brutas dispensam o ORM, mas perdem a portabilidade entre bancos.
Analisar consultas com django-debug-toolbar
A ferramenta django-debug-toolbar permite visualizar todas as consultas SQL geradas por uma view do Django.
# settings.py - Configuração da debug toolbar
INSTALLED_APPS = [
# ... outras aplicações
'debug_toolbar',
]
MIDDLEWARE = [
'debug_toolbar.middleware.DebugToolbarMiddleware',
# ... outros middlewares
]
# Mostrar a toolbar para requisições locais
INTERNAL_IPS = ['127.0.0.1']
DEBUG_TOOLBAR_PANELS = [
'debug_toolbar.panels.sql.SQLPanel', # Consultas SQL
'debug_toolbar.panels.timer.TimerPanel', # Tempo de execução
'debug_toolbar.panels.cache.CachePanel', # Cache
]Essa configuração ativa os painéis mais úteis para a otimização.
# Logging das consultas SQL em desenvolvimento
# settings.py
LOGGING = {
'version': 1,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'level': 'DEBUG',
'handlers': ['console'],
},
},
}Esse logging exibe cada consulta SQL no console, útil para identificar problemas N+1.
Cachear QuerySets
Para dados acessados com frequência e raramente alterados, o cache evita consultas repetitivas.
# views.py
from django.core.cache import cache
from django.views.decorators.cache import cache_page
def list_featured_books(request):
"""Recupera os livros em destaque com cache."""
cache_key = 'featured_books_list'
# Tentativa de recuperação no cache
books = cache.get(cache_key)
if books is None:
# Cache miss: executar a consulta
books = list(
Book.objects.select_related('author')
.filter(featured=True)
.order_by('-published_date')[:10]
)
# Armazenar em cache por 5 minutos
cache.set(cache_key, books, timeout=300)
return render(request, 'books/featured.html', {'books': books})
# Decorador para cachear a view inteira
@cache_page(60 * 15) # Cache de 15 minutos
def list_all_tags(request):
"""Lista todas as tags (dados raramente alterados)."""
tags = Tag.objects.annotate(
book_count=Count('books')
).order_by('-book_count')
return render(request, 'tags/list.html', {'tags': tags})O cache reduz a carga sobre o banco de dados em dados estáticos.
Comece a praticar!
Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.
Conclusão
A otimização de consultas com o ORM do Django apoia-se em alguns princípios fundamentais:
✅ Medir antes de otimizar: usar o django-debug-toolbar para identificar problemas reais
✅ Eliminar o N+1: select_related para ForeignKey, prefetch_related para ManyToMany
✅ Limitar os dados: only(), defer(), values() para carregar apenas o necessário
✅ Indexar com critério: criar índices em colunas frequentemente filtradas ou ordenadas
✅ Cache estratégico: dados pouco modificados se beneficiam do cache do Django
✅ Consultas brutas: como último recurso para otimizações específicas do banco
Aplicadas com método, essas técnicas transformam uma aplicação Django lenta em um sistema eficiente, capaz de lidar com grandes volumes de dados. O ORM do Django continua sendo uma ferramenta poderosa quando suas nuances são dominadas.
Comece a praticar!
Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.
Tags
Compartilhar
Artigos relacionados

Django 5.2: Middleware Personalizado e Tratamento de Signals para Entrevistas Técnicas
Guia completo sobre middleware personalizado e signals no Django 5.2. Implementação de middleware de logging, middleware assíncrono, signals pre_save/post_save e perguntas frequentes de entrevista.

Perguntas de Entrevista Django e Python: Top 25 em 2026
As 25 perguntas mais frequentes em entrevistas de Django e Python. ORM, views, middlewares, DRF, signals e otimizacao com respostas detalhadas e exemplos de codigo.

Django 5: Construindo uma API REST com Django REST Framework
Guia completo para construir uma API REST profissional com Django 5 e DRF. Serializers, ViewSets, autenticacao JWT e boas praticas explicadas passo a passo.