Django - 查询重复/低效

时间:2009-02-18 03:12:29

标签: python django

好吧,我有一个Django视图,像这样:

@render_to('home/main.html')
def login(request):
    # also tried Client.objects.select_related().all()
    clients = Client.objects.all()
    return {'clients':clients}

我有一个模板main.html,就像这样:

<ul>
{% for client in clients %}
<li>{{ client.full_name }}</li>
    <ul>
    {% for pet in client.pets.all %}
        <li>{{ pet.full_name }}</li>
    {% endfor %}
    </ul>
{% endfor %}
</ul>

我还打印出基本模板底部sql_queries中的所有查询。当我运行此视图时,会进行以下查询:

SELECT `home_client`.`id`, ... FROM `home_client`;
SELECT `home_pet`.`id`, ... FROM `home_pet` WHERE `home_pet`.`client_id` = 1;
SELECT `home_client`.`id`, ... FROM `home_client` WHERE `home_client`.`id` = 1;
SELECT `home_client`.`id`, ... FROM `home_client` WHERE `home_client`.`id` = 1;
SELECT `home_pet`.`id`, ... FROM `home_pet` WHERE `home_pet`.`client_id` = 2;
SELECT `home_client`.`id`, ... FROM `home_client` WHERE `home_client`.`id` = 2; 

我的问题是,为什么要进行所有这些查询?它不应该只是1个查询来检索所有客户端和每个客户端的查询以从每个客户端检索所有宠物?我在home_client表中有2个客户端,因此总共应该有3个查询。最令人不安的是查询3和4是100%相同的。我不想“过早地优化”或任何其他东西,但我确实希望确保Django的效率不高。任何有关这方面的帮助将不胜感激。感谢。

3 个答案:

答案 0 :(得分:7)

Django使用缓存。 RDBMS使用缓存。不要过早地优化查询。

您可以在视图功能中使用批量查询,而不是在模板中使用一次一个查询。

@render_to('home/main.html')
def login(request):
    # Query all clients 
    clients = Client.objects.all()
    # Assemble an in-memory table of pets
    pets = collections.defaultdict(list)
    for p in Pet.objects.all():
        pets[pet.client].append(p)
    # Create clients and pets tuples
    clientsPetTuples = [ (c,pets[c]) for c in clients ]
    return {'clientPets': clientsPetTuples}

但是,您似乎没有任何证据表明您的模板是应用程序中最慢的部分。

此外,这会对SQL使用的巨大内存使用进行折衷。在您进行测量以证明模板查询实际上很慢之前,您不应该过度思考SQL。

在有证据之前不要担心SQL。

答案 1 :(得分:4)

尝试使用Client.objects.all()。select_related()

这将自动地在单个数据库查询中缓存相关模型。

答案 2 :(得分:3)

客户1有2个宠物,而客户2有1个宠物吗?

如果是这样,那就表明我在Pet显示循环中正在进行的Pet.full_name或其他事情正在尝试访问其相关客户的详细信息。 Django的ORM不使用identity map,因此从任何Pet对象访问Client外键都需要再次访问数据库以检索该Client。

P.S。 select_related对您在此方案中使用的数据不会产生任何影响,因为它只遵循外键关系,但宠物与客户的关系是多对一的。

更新:如果您想避免更改Pet.full_name中的逻辑或者必须在模板中执行所述逻辑而不是这种情况,您可以改变您获得的方式处理每个客户的宠物,以便为每个宠物与其客户预先填写ForeignKey缓存:

class Client(models.Model):
    # ...
    def get_pets(self):
        for pet in self.pets.all():
            setattr(pet, '_client_cache', self)
            yield pet

...其中'client' '_client_cache'部分是Pet的类中用于PetK客户端的ForeignKey的属性名称。这利用了Django使用其SingleRelatedObjectDescriptor类实现对ForeignKey相关对象的访问的方式,该类在查询数据库之前查找此缓存属性。

产生的模板用法:

{% for pet in client.get_pets %}
...
{% endfor %}