【问题标题】:Django - queries made repeat/inefficientDjango - 查询重复/低效
【发布时间】:2023-03-22 19:22:01
【问题描述】:

好的,我有一个 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 不会非常低效。对此的任何帮助将不胜感激。谢谢。

【问题讨论】:

    标签: python django


    【解决方案1】:

    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。

    【讨论】:

    • 批量查询是指在视图中做select_related()吗?
    • 感谢您的样品。我知道过早的优化是邪恶的,我主要想知道为什么会这样,想确保这不是我的错。
    • 在您衡量性能之前,这不是您的错。在您测量并证明查询很慢之后,那是您的错。在你测量之前,你真的什么都知道
    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      客户 1 有 2 只宠物,客户 2 有 1 只宠物吗?

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

      附: 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_cache''client' 部分是 Pet 类中用于 Pet 客户端的 ForeignKey 的任何属性名称。这利用了 Django 使用其SingleRelatedObjectDescriptor 类实现对 ForeignKey 相关对象的访问的方式,该类在查询数据库之前查找此缓存属性。

      产生的模板使用:

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

      【讨论】:

      • 是的,宠物只有名字,姓氏来自客户的姓氏。发生这种情况有什么大不了的,还是 Django 缓存以及所有这些都做得很好,我可以在晚上睡觉,以我的方式访问姓氏,还是我自己更好地拼凑全名?
      • Django 确实做了一些缓存,但是每个 Pet 的客户端的缓存都在 Pet 实例中。根据您的操作,您将获得(1 + 客户数量 + 宠物数量)对该页面的查询。有了我的建议,您最终会得到(1 + 客户查询数)。使用 S.Lott 的:2 个查询。
      • 感谢您的帮助。代码采样工作。我会从这里权衡我的选择。
      猜你喜欢
      • 2020-05-03
      • 2011-07-28
      • 1970-01-01
      • 2011-12-13
      • 2017-11-03
      • 2017-06-13
      • 2013-01-28
      • 1970-01-01
      • 2017-12-07
      相关资源
      最近更新 更多