Otimização e Gerenciamento Avançado de Modelos Django

Explorar os recursos avançados dos objetos QuerySet do Django é fundamental para construir aplicações eficientes e robustas. Desde a avaliação preguiçosa até estratégias de otimização de consultas, compreender esses mecanismos é crucial para desenvolvedores.

Objetos QuerySet no Django

Fatiamento (Slicing)

Os QuerySets permitem a utilização da sintaxe de fatiamento do Python para limitar o número de registros retornados. Esta funcionalidade é análoga às cláusulas LIMIT e OFFSET em SQL.

from myapp.models import Entry

# Retorna os 5 primeiros registros (LIMIT 5)
primeiros_cinco = Entry.objects.all()[:5]

# Retorna 5 registros a partir do sexto (OFFSET 5 LIMIT 5)
proximos_cinco = Entry.objects.all()[5:10]

É importnate notar que índices negativos (por exemplo, Entry.objects.all()[-1]) não são suportados. Geralmente, o fatiamento de um QuerySet resulta em um novo QuerySet, sem que uma consulta ao banco de dados seja executada imediatamente.

Iteração

Um QuerySet pode ser iterado, permitindo o acesso fácil a cada objeto retornado pela consulta.

from myapp import models

lista_artigos = models.Article.objects.all()

for artigo in lista_artigos:
    print(artigo.title)

Avaliação Preguiçosa (Lazy Evaluation)

Os QuerySets do Django são avaliados de forma "preguiçosa". Isso significa que a criação de um QuerySet não implica em nenhuma interação com o banco de dados. A consulta só será executada quando os resultados do QuerySet forem realmente necessários.

from myapp import models

# Nenhuma consulta ao banco de dados é feita neste ponto
resultado_consulta = models.Article.objects.all()

# A consulta é executada e os resultados são exibidos
print(resultado_consulta)

# A consulta é executada (se ainda não estiver em cache) e os resultados são iterados
for item_artigo in resultado_consulta:
    print(item_artigo.title)

Em essência, a interação com o banco de dados ocorre somente quando o QuerySet é "solicitado" para fornecer seus dados, ou seja, quando é avaliado.

Mecanismo de Cache

Todo QuerySet possui um mecanismo de cache interno para minimizar as consultas ao banco de dados. Compreender seu funcionamento é vital para otimizar o código.

Quando um QuerySet é recém-criado, seu cache está vazio. Na primeira avaliação de um QuerySet — o que dispara a consulta ao banco de dados — o Django armazena os resultados no cache e retorna os dados solicitados. Avaliações subsequentes do mesmo QuerySet reutilizarão os resultados do cache.

Este comportamento de cache pode levar a ineficiências se não for compreendido. Considere o exemplo a seguir, que executa duas consultas idênticas ao banco de dados:

from myapp import models

# Primeira consulta ao DB para obter títulos
print([item.title for item in models.Article.objects.all()])
# Segunda consulta ao DB para obter tempos de criação
print([item.create_time for item in models.Article.objects.all()])

Para evitar esta duplicação e garantir a consistência dos dados, salve o QuerySet e reutilize-o:

from myapp import models

meus_artigos = models.Article.objects.all()
# Apenas uma consulta ao DB é feita aqui
print([item.title for item in meus_artigos])
print([item.create_time for item in meus_artigos])

Quando o Cache do QuerySet não é Preenchido?

O cache não é preenchido em todas as situações. Quando apenas uma porção do QuerySet é avaliada (por exemplo, usando fatiamento ou indexação), o cache é verificado, mas os registros subsequentes não serão armazenados. Portanto, acessar um índice específico repetidamente resultará em múltiplas consultas ao banco de dados:

from myapp.models import Entry

conjunto_dados = Entry.objects.all()
print(conjunto_dados[5]) # Consulta o banco de dados
print(conjunto_dados[5]) # Consulta o banco de dados novamente

No entanto, se o QuerySet completo já tiver sido avaliado, o cache será utilizado:

from myapp.models import Entry

conjunto_dados = Entry.objects.all()
[item_entry for item_entry in conjunto_dados] # Consulta o banco de dados e preenche o cache
print(conjunto_dados[5]) # Usa o cache
print(conjunto_dados[5]) # Usa o cache

Outras operações que avaliam e perenchem o cache do QuerySet completo incluem:

  • [item for item in queryset]
  • bool(queryset)
  • item in queryset
  • list(queryset)

Nota: Apenas imprimir o QuerySet (print(queryset)) não preenche o cache. Isso fará uma nova consulta cada vez que for impresso.

Métodos exists() e iterator()

exists()

Uma simples verificação if queryset: pode disparar a avaliação completa do QuerySet e armazenar todos os dados no cache, mesmo que você só precise saber se há algum resultado. Para evitar isso, use exists() para uma verificação otimizada:

if resultado_consulta.exists():
    # Isso gera um SQL como SELECT (1) AS "a" FROM "blog_article" LIMIT 1;
    print("Existem registros...")

iterator()

Para QuerySets muito grandes, o cache pode se tornar um problema, consumindo muita memória. O método iterator() evita a criação do cache, permitindo que os dados sejam processados e descartados de forma eficiente.

from myapp.models import Book

objetos_livro = Book.objects.all().iterator()
# iterator() busca dados em lotes menores, economizando memória.
for livro in objetos_livro:
    print(livro.title)

# IMPORTANTE: Re-iterar 'objetos_livro' não funcionará, pois o iterador já chegou ao fim.
# Seria necessário criar um novo iterador para uma segunda passagem.

Ao usar iterator(), esteja ciente de que a ausência de cache significa que cada iteração de um QuerySet poderá disparar novas consultas ao banco de dados. Use-o com cautela, garantindo que não está executando consultas repetidas em contextos onde o cache seria benéfico.

Em resumo, o cache do QuerySet otimiza consultas ao banco de dados em uso geral, executando-as apenas quando necessário. exists() e iterator() oferecem otimizações para cenários específicos de uso de memória, mas podem levar a consultas adicionais se o cache for esperado.

Modelos Intermediários (Many-to-Many com through)

Para relacionamentos muitos-para-muitos simples (como "pizza" e "ingredientes"), um ManyToManyField padrão é suficiente. No entanto, às vezes é necessário associar informações adicionais ao próprio relacionamento. Por exemplo, em um grupo musical, além de saber quem são os membros, você pode querer registrar a data de entrada de cada membro ou o motivo do convite.

Nesses casos, o Django permite especificar um modelo intermediário usando o parâmetro through no ManyToManyField. Este modelo intermediário pode incluir campos adicionais que descrevem a relação. Abaixo, um exemplo para grupos musicais:

from django.db import models
from datetime import date

class Pessoa(models.Model):
    nome = models.CharField(max_length=128)

    def __str__(self):
        return self.nome

class Grupo(models.Model):
    nome = models.CharField(max_length=128)
    membros = models.ManyToManyField(Pessoa, through='MembresiaGrupo')

    def __str__(self):
        return self.nome

class MembresiaGrupo(models.Model):
    pessoa = models.ForeignKey(Pessoa, on_delete=models.CASCADE)
    grupo = models.ForeignKey(Grupo, on_delete=models.CASCADE)
    data_entrada = models.DateField()
    motivo_convite = models.CharField(max_length=64)

    class Meta:
        # Garante que uma pessoa não possa ter múltiplas membresias no mesmo grupo
        unique_together = (('pessoa', 'grupo'),)

Para criar relacionamentos utilizando um modelo intermediário, você deve criar instâncias desse modelo. Os métodos add(), create() e a atribuição direta a ManyToManyField (por exemplo, grupo.membros = [...]) não são permitidos porque eles não podem fornecer os dados adicionais exigidos pelo modelo intermediário.

from datetime import date
from myapp.models import Pessoa, Grupo, MembresiaGrupo

ringo = Pessoa.objects.create(nome="Ringo Starr")
paul = Pessoa.objects.create(nome="Paul McCartney")
os_beatles = Grupo.objects.create(nome="The Beatles")

# Criando a membresia para Ringo
m1 = MembresiaGrupo(
    pessoa=ringo,
    grupo=os_beatles,
    data_entrada=date(1962, 8, 16),
    motivo_convite="Precisava de um novo baterista."
)
m1.save()

print(os_beatles.membros.all())
# [<pessoa: ringo="" starr="">]
print(ringo.grupo_set.all())
# [<grupo: beatles="" the="">]

# Criando a membresia para Paul
m2 = MembresiaGrupo.objects.create(
    pessoa=paul,
    grupo=os_beatles,
    data_entrada=date(1960, 8, 1),
    motivo_convite="Queria formar uma banda."
)

print(os_beatles.membros.all())
# [<pessoa: ringo="" starr="">, <pessoa: mccartney="" paul="">]
</pessoa:></pessoa:></grupo:></pessoa:>

De forma similar, o método remove() é desabilitado. No entanto, o método clear() pode ser usado para remover todos os relacionamentos de um dado objeto, o que implica na exclusão das instâncias do modelo intermediário associadas.

# The Beatles se separaram
os_beatles.membros.clear()
# Note que isso deleta as instâncias do modelo intermediário
print(MembresiaGrupo.objects.all())
# <QuerySet []>

Otimização de Consultas

As definições de modelos a seguir servirão de base para os exemplos de otimização de consultas:

from django.contrib.auth.models import AbstractUser
from django.db import models

class UserInfo(AbstractUser):
    """ Informações do Usuário """
    nid = models.BigAutoField(primary_key=True)
    nickname = models.CharField(verbose_name='Apelido', max_length=32)
    telephone = models.CharField(max_length=11, blank=True, null=True, unique=True, verbose_name='Telefone')
    avatar = models.FileField(verbose_name='Avatar',upload_to = 'avatar/',default="/avatar/default.png")
    created_at = models.DateTimeField(verbose_name='Data de Criação', auto_now_add=True)

    fans = models.ManyToManyField(verbose_name='Fãs',
                                  to='UserInfo',
                                  through='UserFans',
                                  related_name='f',
                                  through_fields=('user', 'follower'))

    def __str__(self):
        return self.username

class UserFans(models.Model):
    """ Tabela de Relacionamento de Fãs """
    nid = models.AutoField(primary_key=True)
    user = models.ForeignKey(verbose_name='Blogueiro', to='UserInfo', to_field='nid', related_name='users', on_delete=models.CASCADE)
    follower = models.ForeignKey(verbose_name='Seguidor', to='UserInfo', to_field='nid', related_name='followers', on_delete=models.CASCADE)

class Blog(models.Model):
    """ Informações do Blog """
    nid = models.BigAutoField(primary_key=True)
    title = models.CharField(verbose_name='Título do Blog Pessoal', max_length=64)
    site = models.CharField(verbose_name='Sufixo do Blog Pessoal', max_length=32, unique=True)
    theme = models.CharField(verbose_name='Tema do Blog', max_length=32)
    user = models.OneToOneField(to='UserInfo', to_field='nid', on_delete=models.CASCADE)
    def __str__(self):
        return self.title

class Category(models.Model):
    """ Tabela de Categorias de Artigos do Blogueiro """
    nid = models.AutoField(primary_key=True)
    title = models.CharField(verbose_name='Título da Categoria', max_length=32)
    blog = models.ForeignKey(verbose_name='Blog Pertencente', to='Blog', to_field='nid', on_delete=models.CASCADE)

class Article(models.Model):
    nid = models.BigAutoField(primary_key=True)
    title = models.CharField(max_length=50, verbose_name='Título do Artigo')
    description = models.CharField(max_length=255, verbose_name='Descrição do Artigo')
    read_count = models.IntegerField(default=0)
    comment_count= models.IntegerField(default=0)
    up_count = models.IntegerField(default=0)
    down_count = models.IntegerField(default=0)
    category = models.ForeignKey(verbose_name='Tipo de Artigo', to='Category', to_field='nid', null=True, on_delete=models.SET_NULL)
    created_at = models.DateField(verbose_name='Data de Criação')
    blog = models.ForeignKey(verbose_name='Blog Pertencente', to='Blog', to_field='nid', on_delete=models.CASCADE)
    tags = models.ManyToManyField(
        to="Tag",
        through='Article2Tag',
        through_fields=('article', 'tag'),
)

class ArticleDetail(models.Model):
    """ Tabela de Detalhes do Artigo """
    nid = models.AutoField(primary_key=True)
    content = models.TextField(verbose_name='Conteúdo do Artigo')
    article = models.OneToOneField(verbose_name='Artigo Associado', to='Article', to_field='nid', on_delete=models.CASCADE)

class Comment(models.Model):
    """ Tabela de Comentários """
    nid = models.BigAutoField(primary_key=True)
    article = models.ForeignKey(verbose_name='Artigo Comentado', to='Article', to_field='nid', on_delete=models.CASCADE)
    content = models.CharField(verbose_name='Conteúdo do Comentário', max_length=255)
    created_at = models.DateTimeField(verbose_name='Data de Criação', auto_now_add=True)
    parent_comment = models.ForeignKey('self', blank=True, null=True, verbose_name='Comentário Pai', on_delete=models.CASCADE)
    user = models.ForeignKey(verbose_name='Comentarista', to='UserInfo', to_field='nid', on_delete=models.CASCADE)
    up_count = models.IntegerField(default=0)

    def __str__(self):
        return self.content

class ArticleUpDown(models.Model):
    """ Tabela de Curtidas/Descurtidas de Artigos """
    nid = models.AutoField(primary_key=True)
    user = models.ForeignKey('UserInfo', null=True, on_delete=models.CASCADE)
    article = models.ForeignKey("Article", null=True, on_delete=models.CASCADE)
    is_up = models.BooleanField(verbose_name='É Curtida?') # Renomeado para maior clareza

class CommentUp(models.Model):
    """ Tabela de Curtidas de Comentários """
    nid = models.AutoField(primary_key=True)
    user = models.ForeignKey('UserInfo', null=True, on_delete=models.CASCADE)
    comment = models.ForeignKey("Comment", null=True, on_delete=models.CASCADE)

class Tag(models.Model):
    nid = models.AutoField(primary_key=True)
    title = models.CharField(verbose_name='Nome da Tag', max_length=32)
    blog = models.ForeignKey(verbose_name='Blog Pertencente', to='Blog', to_field='nid', on_delete=models.CASCADE)

class Article2Tag(models.Model):
    nid = models.AutoField(primary_key=True)
    article = models.ForeignKey(verbose_name='Artigo', to="Article", to_field='nid', on_delete=models.CASCADE)
    tag = models.ForeignKey(verbose_name='Tag', to="Tag", to_field='nid', on_delete=models.CASCADE)

select_related()

Uso Básico

Para campos OneToOneField e ForeignKey, select_related() é uma ferramenta de otimização poderosa. Ele retorna um QuerySet que, ao ser executado, realiza um JOIN com as tabelas relacionadas, buscando os dados dos objetos relacionados em uma única consulta. Isso evita consultas subsequentes ao banco de dados ao acessar esses objetos relacionados.

Considere o cenário de buscar o título da categoria de um artigo com nid=2 sem select_related():

from myapp import models

# Primeira consulta ao banco de dados para o artigo
artigo = models.Article.objects.get(nid=2)

# Segunda consulta ao banco de dados para obter o objeto Category relacionado
print(artigo.category.title)

Isso resultaria em duas consultas SQL. Com select_related(), podemos reduzir para uma:

from myapp import models

lista_artigos_com_categoria = models.Article.objects.select_related("category").all()

for artigo_obj in lista_artigos_com_categoria:
    # Não há nova consulta ao banco de dados, pois 'article_obj.category'
    # foi pré-carregado na consulta inicial.
    print(artigo_obj.category.title)

A consulta SQL gerada seria um LEFT OUTER JOIN entre blog_article e blog_category.

Consulta com Múltiplas Chaves Estrangeiras

Para otimizar o acesso a múltiplos campos relacionados, você pode passar vários argumentos para select_related().

from myapp import models

# Otimizando para 'category' e 'articledetail'
artigo_completo = models.Article.objects.select_related("category", "articledetail").get(nid=1)
print(artigo_completo.articledetail.content)

A partir do Django 1.7, também é possível encadear chamadas a select_related():

from myapp import models

artigo_completo_encadeado = models.Article.objects \
                            .select_related("category") \
                            .select_related("articledetail") \
                            .get(nid=1)
print(artigo_completo_encadeado.articledetail.content)

Ambas as abordagens resultariam em uma única consulta SQL com múltiplos LEFT OUTER JOIN para incluir as tabelas blog_category e blog_articledetail.

Consultas Aninhadas (Deep Lookups)

Para acessar relacionamentos de forma mais profunda, utilize a sintaxe de duplo sublinhado (__).

from myapp import models

# Consulta o nome de usuário do autor do artigo com nid=1
# Usando "__" para seguir o relacionamento 'blog' até 'user'
artigo_com_blog_usuario = models.Article.objects.select_related("blog__user").get(nid=1)
print(artigo_com_blog_usuario.blog.user.username)

Isso geraria uma única consulta SQL com INNER JOINs entre blog_article, blog_blog e blog_userinfo.

Resumo de select_related()

  1. É ideal para otimizar relacionamentos um-para-um (OneToOneField) e muitos-para-um (ForeignKey).
  2. Utiliza cláusulas JOIN em SQL para buscar dados relacionados em uma única consulta, reduzindo o número total de interações com o banco.
  3. Aceita múltiplos campos como argumentos ou pode-se usar __ para especificar relacionamentos aninhados.
  4. Campos ou profundidades não especificados não são pré-carregados e, se acessados, gerarão consultas adicionais.
  5. Em Django >= 1.7, chamadas encadeadas de select_related() são equivalentes a passar múltiplos argumentos. Versões anteriores podem sobrescrever chamadas anteriores.

prefetch_related()

Ao contrário de select_related(), que usa JOINs, prefetch_related() é ideal para relacionamentos muitos-para-muitos (ManyToManyField) e um-para-muitos (relação inversa de ForeignKey, também conhecida como "relacionamento reverso").

Ele resolve o problema de múltiplas consultas realizando consultas separadas para cada tabela e, em seguida, unindo os resultados no Python. Isso evita tabelas JOINs excessivamente grandes que seriam ineficientes para muitos-para-muitos.

Exemplo sem prefetch_related() para buscar todas as tags associadas a cada artigo:

from myapp import models

artigos = models.Article.objects.all()
for artigo in artigos:
    # Para cada artigo, uma nova consulta ao DB é feita para buscar as tags
    print(artigo.tags.all())
# Se houver N artigos, serão N+1 consultas ao DB

Com prefetch_related():

from myapp import models

artigos_com_tags = models.Article.objects.prefetch_related("tags").all()
for artigo in artigos_com_tags:
    # As tags já foram pré-carregadas em uma ou poucas consultas adicionais
    print(artigo.tags.all())
# Apenas 2 consultas ao DB (uma para artigos, outra para tags e a tabela intermediária)

As consultas SQL geradas seriam:

-- Primeira consulta para os artigos
SELECT "blog_article"."nid", "blog_article"."title", ... FROM "blog_article";

-- Segunda consulta para as tags relacionadas aos artigos encontrados
SELECT
  ("blog_article2tag"."article_id") AS "_prefetch_related_val_article_id",
  "blog_tag"."nid",
  "blog_tag"."title",
  "blog_tag"."blog_id"
FROM "blog_tag"
INNER JOIN "blog_article2tag" ON ("blog_tag"."nid" = "blog_article2tag"."tag_id")
WHERE "blog_article2tag"."article_id" IN (1, 2, 3, 4); -- IDs dos artigos da primeira consulta

Em síntese, select_related é para relacionamentos de chave estrangeira simples (muitos-para-um, um-para-um) usando JOINs de SQL, enquanto prefetch_related é para relacionamentos muitos-para-muitos e inversos de chave estrangeira, fazendo consultas separadas e unindo no Python. A escolha depende da cardinalidade do relacionamento e do impacto no desempenho do banco de dados.

extra() para SQL Personalizado

O método extra() permite injetar cláusulas SQL personalizadas em um QuerySet, oferecendo flexibilidade para consultas que são difíceis de expressar com a API padrão do Django. Deve ser usado com cautela devido a possíveis problemas de portabilidade entre diferentes sistemas de banco de dados.

extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)

É necessário usar pelo menos um dos parâmetros.

Parâmetro select

O parâmetro select permite adicionar campos calculados à cláusula SELECT. Deve ser um dicionário mapeando nomes de atributos para cláusulas SQL.

from myapp import models

artigos_extra = models.Article.objects.extra(
    select={'is_recent': "created_at > '2023-01-01'"}
)
# Cada objeto 'Article' no resultado terá um atributo 'is_recent' booleano.

Exemplo com formatação de data (SQLite):

from myapp import models

artigo_com_data_formatada = models.Article.objects \
                                .filter(nid=1) \
                                .extra(select={"standard_time":"strftime('%%Y-%%m-%%d', created_at)"}) \
                                .values("standard_time", "nid", "title")
print(artigo_com_data_formatada)
# Exemplo de saída: <QuerySet [{'title': 'Título do Artigo', 'standard_time': '2023-09-03', 'nid': 1}]>

Parâmetros where e tables

Use where para definir cláusulas WHERE SQL explícitas e tables para adicionar tabelas manualmente à cláusula FROM. Ambos aceitam listas de strings, e as condições de where são combinadas com AND a quaisquer outras condições de filtragem.

from myapp import models

resultados_personalizados = models.Article.objects.extra(
    where=['nid IN (1, 3) OR title LIKE "py%" ', 'nid > 2']
)

Exemplo mais complexo com select, where, tables e order_by:

from myapp import models

# Assume que 'UserInfo' e 'UserType' são modelos relacionados em 'app01'
# Este exemplo é ilustrativo para demonstração das capacidades do `extra`
resultados_avancados = models.UserInfo.objects.extra(
    select={'total_usertypes_maior_que_1': 'SELECT COUNT(1) FROM app01_usertype WHERE id > %s'},
    select_params=[1], # Parâmetro para a subconsulta 'select'
    where=['age > %s'],
    params=[18], # Parâmetro para a cláusula 'where'
    order_by=['-age'],
    tables=['app01_usertype'] # Adiciona 'app01_usertype' à cláusula FROM
)
# A consulta SQL resultante seria uma combinação destas cláusulas.

SQL Puro (Raw SQL)

Para controle total sobre as consultas, o Django oferece maneiras de executar SQL puro.

Usando connection.cursor()

Para executar SQL diretamente e obter um cursor do banco de dados:

from django.db import connection, connections

# Obtém o cursor da conexão padrão
cursor_default = connection.cursor()
# Ou um cursor de uma conexão nomeada (e.g., 'db2' em settings.py)
# cursor_db2 = connections['db2'].cursor()

cursor_default.execute("""SELECT * FROM auth_user WHERE id = %s""", [1])

# Obtém um único resultado
registro_unico = cursor_default.fetchone()
# Obtém todos os resultados
todos_registros = cursor_default.fetchall()

Usando Model.objects.raw()

Para executar SQL puro e ter os resultados mapeados para instâncias do modelo, use Model.objects.raw():

from myapp import models

autores_raw = models.Author.objects.raw('SELECT * FROM app01_author WHERE nid > 1')
for autor in autores_raw:
    print(autor.name)
# O Django tentará mapear as colunas da consulta aos campos do modelo Author.

# CUIDADO: Se a consulta RAW incluir campos que NÃO existem no modelo
# associado, eles podem ser acessados, mas a operação é menos robusta.
# Exemplo (NÃO RECOMENDADO para produção):
livros_raw = models.Book.objects.raw('SELECT * FROM app01_book WHERE nid > 1')
for livro in livros_raw:
    print(livro.price) # 'price' é um campo de Book
    # print(livro.title) # 'title' é um campo de Book
    # O tipo de objeto retornado ainda será uma instância do modelo Book.

Inserção em Massa (Bulk Insertion)

Para reduzir o número de consultas SQL ao criar múltiplos objetos, utilize bulk_create().

from myapp.models import Entry

# Cria múltiplos objetos Entry em uma única operação de banco de dados
Entry.objects.bulk_create([
    Entry(headline="Python 3.0 Lançado"),
    Entry(headline="Python 3.1 Planejado")
])

Isto é consideravelmente mais eficiente do que:

from myapp.models import Entry

Entry.objects.create(headline="Python 3.0 Lançado")
Entry.objects.create(headline="Python 3.1 Planejado")

Note que bulk_create() possui algumas ressalvas, como a não execução de sinais de modelo (signals) de salvamento, por isso verifique a documentação para detalhes. A mesma lógica se aplica a campos ManyToManyField, onde adicionar múltiplos objetos de uma vez é mais eficiente:

from myapp.models import Band, Artist # Exemplo de modelos com ManyToMany

minha_banda = Band.objects.get(id=1)
eu = Artist.objects.get(id=1)
meu_amigo = Artist.objects.get(id=2)

# Adiciona múltiplos membros em uma única operação
minha_banda.members.add(eu, meu_amigo)

É preferível a:

minha_banda.members.add(eu)
minha_banda.members.add(meu_amigo)

Operações Transacionais

Para garantir a atomicidade de um conjunto de operações de banco de dados, use o gerenciador de contexto de transação do Django. Se qualquer operação dentro do bloco falhar, todas as mudanças são revertidas (rollback).

from django.db import transaction

with transaction.atomic():
    # Todas as operações de banco de dados aqui farão parte da mesma transação.
    # Se uma exceção for levantada, a transação será revertida.
    # Caso contrário, a transação será confirmada (commit) ao final do bloco.
    # Ex:
    # livro = models.Book.objects.create(title="Novo Livro")
    # livro.save()

defer() e only()

Esses métodos são usados para controlar quais campos de um modelo são carregados do banco de dados, otimizando o uso de memória e a velocidade de consulta quando você não precisa de todos os dados de um objeto.

defer('field1', 'field2', ...)

O método defer() instrui o Django a não carregar os campos especificados do banco de dados *imediatamente*. Quando um objeto é recuperado, esses campos não são incluídos na consulta inicial. Se você tentar acessar um campo deferido, o Django fará uma consulta adicional ao banco de dados para buscá-lo.

from myapp import models

# Carrega objetos Author, mas adia o carregamento dos campos 'bio' e 'email'
autores_diferidos = models.Author.objects.defer('bio', 'email')
for autor in autores_diferidos:
    print(autor.name) # 'name' é carregado inicialmente
    # Se 'autor.bio' for acessado, uma nova consulta ao DB será feita APENAS para aquele campo.

only('field1', 'field2', ...)

O método only() funciona de forma oposta a defer(). Ele especifica *apenas* os campos que devem ser carregados do banco de dados na consulta inicial. Qualquer campo não listado em only() será tratado como deferido, resultando em uma consulta adicional se for acessado.

from myapp import models

# Carrega objetos Author, incluindo APENAS o campo 'nid'
autores_apenas_nid = models.Author.objects.only('nid')
for autor in autores_apenas_nid:
    print(autor.nid) # 'nid' é carregado inicialmente

    # CUIDADO: Acessar 'autor.name' (ou qualquer outro campo não incluído em 'only')
    # causará uma consulta adicional ao banco de dados, o que pode anular a otimização.
    # print(autor.name) # Isso dispara uma nova consulta ao DB para cada autor!

A moral é: use defer() ou only() quando tiver certeza de que não precisará de certos campos ou precisará apenas de um subconjunto específico, e esteja atento ao "custo" de acessar campos que não foram carregados inicialmente.

Tags: Django QuerySet Otimização database Python

Publicado em 7-15 00:09