# 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. - Published: 2026-02-15 - Updated: 2026-04-28 - Author: SharpSkill - Tags: django, django orm, python, otimização banco de dados, desempenho - Reading time: 14 min --- 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. > **A regra de ouro da otimização** > > 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. ```python # 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.title ``` Esses modelos simples permitem ilustrar o problema N+1 de forma concreta. ```python # 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. ```python # 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. ```python # 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. ```python # 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. ```python # 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. > **select_related vs prefetch_related** > > 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. ```python # 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. ```python # 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. ## 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. ```python # 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. ```python # 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. ```python # 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. ```python # 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. ```python # 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 e operações de escrita** > > Í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. ```python # 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. ```python # 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. ```python # 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. ```python # 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. ## 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. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pt/blog/django/django-orm-optimizing-queries