Otimizando projetos em Django com select_related

Um dos problemas que vemos na utilização “padrão” do Django é a ausência do famigerado join nas consultas geradas pelo ORM do framework. Isto pode ser notado em um exemplo clássico: uma classe de modelo, chamada Pessoa, que tem um relacionamento com outra classe de modelo chamada Carro, bem simples, uma pessoa tem um carro. São executadas duas consultas no banco de dados quando o seguinte código é executado:

p = Pessoa.objects.get(id=1)
carro = p.carro

Na primeira consulta, o Django busca na tabela de pessoas um registro único no qual o atributo id tem valor 1. Na segunda consulta, o Django busca na tabela de carros o carro que possui o valor da foreign key correspondente ao id do objeto p (instância da classe Pessoa).

Partindo deste pressuposto, é possível ver um número muito grande de problemas. Se for dado um novo modelo de classes, com seis classes: Cliente, Bairro, Cidade, UF, Pais e Continente. Essas classes possuem relacionamentos em cascata: Cliente com Bairro, Bairro com Cidade, e assim sucessivamente, até Continente. Qual seria o comportamento do Django ao tentar acessar o continente de um determinado cliente? O interpretador interativo do Python responde esta questão de maneira bem simples:

>>> cliente = Cliente.objects.get(id=1)
>>> continente = cliente.bairro.cidade.uf.pais.continente
>>> from django.db import connection
>>> queries = connection.queries
>>> for query in queries:
...    print query
...
{'time': '0.003', 'sql': u'SELECT "clientes_cliente"."id", "clientes_cliente"."nome", "clientes_cliente"."idade", "clientes_cliente"."bairro_id" FROM "clientes_cliente" WHERE "clientes_cliente"."id" = 1 '}
{'time': '0.000', 'sql': u'SELECT "clientes_bairro"."id", "clientes_bairro"."nome", "clientes_bairro"."cidade_id" FROM "clientes_bairro" WHERE "clientes_bairro"."id" = 1 '}
{'time': '0.000', 'sql': u'SELECT "clientes_cidade"."id", "clientes_cidade"."nome", "clientes_cidade"."uf_id" FROM "clientes_cidade" WHERE "clientes_cidade"."id" = 1 '}
{'time': '0.000', 'sql': u'SELECT "clientes_uf"."id", "clientes_uf"."sigla", "clientes_uf"."pais_id" FROM "clientes_uf" WHERE "clientes_uf"."id" = 1 '}
{'time': '0.000', 'sql': u'SELECT "clientes_pais"."id", "clientes_pais"."nome", "clientes_pais"."continente_id" FROM "clientes_pais" WHERE "clientes_pais"."id" = 1 '}
{'time': '0.000', 'sql': u'SELECT "clientes_continente"."id", "clientes_continente"."nome" FROM "clientes_continente" WHERE "clientes_continente"."id" = 1 '}
>>>

Aí está a resposta quanto ao comportamento do Django: duas linhas de código, seis consultas ao banco de dados. Se esta fosse uma informação muito importante em um sistema, é possível notar o quão doloroso seria manter tudo isso num banco de dados relacional. É necessária uma abordagem diferente, com apenas um join seis consultas virariam apenas uma e o programador poderia dormir sem medo do DBA.

A instrução select_related responde à esta necessidade: ao ver esta instrução, o Django sabe que ele tem que fazer os joins, e que ele deve “passear” por todos os relacionamentos obtendo os dados. Se o cliente for buscado no banco de dados utilizando select_related, o objeto estará completamente em memória, e não será necessário acessar o banco de dados novamente para obter informações de nenhum dos relacionamentos, o interpretador interativo prova isso:

>>> cliente = Cliente.objects.select_related().get(id=1)
>>> continente = cliente.bairro.cidade.uf.pais.continente
>>> from django.db import connection
>>> queries = connection.queries
>>> for query in queries:
...      print query
...
{'time': '0.010', 'sql': u'SELECT "clientes_cliente"."id", "clientes_cliente"."nome", "clientes_cliente"."idade", "clientes_cliente"."bairro_id", "clientes_bairro"."id", "clientes_bairro"."nome", "clientes_bairro"."cidade_id", "clientes_cidade"."id", "clientes_cidade"."nome", "clientes_cidade"."uf_id", "clientes_uf"."id", "clientes_uf"."sigla", "clientes_uf"."pais_id", "clientes_pais"."id", "clientes_pais"."nome", "clientes_pais"."continente_id", "clientes_continente"."id", "clientes_continente"."nome" FROM "clientes_cliente" INNER JOIN "clientes_bairro" ON ("clientes_cliente"."bairro_id" = "clientes_bairro"."id") INNER JOIN "clientes_cidade" ON ("clientes_bairro"."cidade_id" = "clientes_cidade"."id") INNER JOIN "clientes_uf" ON ("clientes_cidade"."uf_id" = "clientes_uf"."id") INNER JOIN "clientes_pais" ON ("clientes_uf"."pais_id" = "clientes_pais"."id") INNER JOIN "clientes_continente" ON ("clientes_pais"."continente_id" = "clientes_continente"."id") WHERE "clientes_cliente"."id" = 1 '}
>>>

Problema de consultas resolvido, DBA feliz = programador feliz = cliente feliz! 🙂


2 Comentários

Deixe seu comentário

Seu endereço de e-mail não será publicado. Campos com * são obrigatórios!