【问题标题】:Django: is it possible to perform queries on a cached QuerySet instead of querying the database?Django:是否可以在缓存的 QuerySet 上执行查询而不是查询数据库?
【发布时间】:2018-02-09 08:37:58
【问题描述】:

我经常需要缓存结果并执行类似 ORM 的操作,但使用 普通 Python 逻辑,因为我不希望数据库因性能问题而受到影响。

出于这个原因,我想知道 Django 是否提供了一种在缓存的 QuerySet 上执行 ORM 操作的方法,而不是每次我们使用的 core 查询略有变化时都查询数据库。

为了形象化我在说什么,假设我们有这些不同的查询:

# when evaluated it hits the db
devs = Worker.objects.filter(job__category=JobCategory.DEVELOPER)

# when evaluated it hits the db again
young_devs_salary = devs.filter(dob__gte=datetime.now() - relativedelta(years=24)).values('dob', 'salary')

# it would hit the db again
wellpaid_devs = devs.filter(salary__gte=high_salary_yearly)

请注意,第一个 QuerySet 将是以下 QuerySets 其余部分的超集。

我所追求的是想办法告诉 Django 只有第一个查询 devs 应该访问数据库,然后缓存其结果,而其他使用 devs 的查询不应该查询数据库,但改为缓存QuerySet

这可能吗?如果 Django 不支持这个,为什么?也许我的情况并不常见?但我有点怀疑,因为我每天都会遇到类似的情况。

【问题讨论】:

    标签: python django database caching


    【解决方案1】:

    正如 Daniel 已经回答的那样,不,如果不最终访问数据库,您就不能以这种方式使用查询 API。现在,您不需要任何特殊的东西来过滤纯 python 中的查询集的结果——这主要是使用标准 python 功能的标准列表过滤——但在这里它实际上可能不会提高性能,如果有的话。避免无用 db 命中很好,但这并不意味着数据库是你的敌人,在大多数情况下,经过适当调整的具有健全架构的数据库可能会胜过纯 python 列表过滤。

    如果 Queryset API 确实支持纯 python 过滤,那么 FWIW 将被实现为标准列表过滤操作等,因此它会给你带来很多好处,恰恰相反。

    以纯python版本为例:

    young_devs_salary = devs.filter(dob__gte=datetime.now() - relativedelta(years=24)).values('dob', 'salary')
    

    应该是:

    target_date = datetime.now() - relativedelta(years=24)
    young_devs_salary = [(dev.dob, dev.salary) for dev in devs if dev.dob >= target_date]
    

    正如您所见,Queryset api 中不需要任何其他内容,但我怀疑这会比查询数据库快得多,除非您的数据集非常小或 django 应用程序和数据库之间的网络连接非常差服务器(或任何其他基础架构/数据库调整问题)。

    【讨论】:

      【解决方案2】:

      不,它不支持它,我很困惑你会问为什么不支持。

      查询在数据库中完成。按照你的要求去做意味着在 Python 代码中实现该功能的完整副本,这将是低效且不必要的。

      【讨论】:

      • 那怎么会是低效的呢?点击缓存结果应该比点击数据库更快。
      • @dabadaba 不一定,在很多情况下甚至完全相反,除非您的数据库和/或基础架构存在严重问题。数据库不仅仅是位桶,它们是高度优化的查询引擎,并且通常会胜过纯 Python 代码。
      【解决方案3】:

      When QuerySets are evaluated

      在内部,一个 QuerySet 可以被构造、过滤、切片,并且通常在不实际访问数据库的情况下传递。在您对查询集进行评估之前,实际上不会发生任何数据库活动。

      您可以通过以下方式评估 QuerySet:

      迭代。 QuerySet 是可迭代的,它会在您第一次迭代它时执行其数据库查询。例如,这将打印数据库中所有条目的标题:

      for e in Entry.objects.all():
          print(e.headline)
      

      注意:如果您只想确定是否存在至少一个结果,请不要使用此选项。使用exists()效率更高。

      切片。正如限制 QuerySets 中所解释的,可以使用 Python 的数组切片语法对 QuerySet 进行切片。对未计算的 QuerySet 进行切片通常会返回另一个未计算的 QuerySet,但如果您使用切片语法的“step”参数,Django 将执行数据库查询,并返回一个列表。对已评估的 QuerySet 进行切片也会返回一个列表。

      还要注意,即使对未计算的 QuerySet 进行切片会返回另一个未计算的 QuerySet,但也不允许对其进行进一步修改(例如,添加更多过滤器或修改排序),因为这不能很好地转化为 SQL 并且不会有明确的意思是。

      酸洗/缓存。有关酸洗 QuerySet 所涉及的内容的详细信息,请参阅以下部分。就本节而言,重要的是从数据库中读取结果。

      repr()。当您在其上调用 repr() 时,将评估 QuerySet。这是 为了方便 Python 交互式解释器,您可以 以交互方式使用 API 时立即查看结果。

      len()。当您在其上调用 len() 时,将评估 QuerySet。这就像你 可能期望,返回结果列表的长度。

      注意:如果您只需要确定集合中的记录数(而不需要实际对象),则使用 SQL 的 SELECT COUNT(*) 在数据库级别处理计数会更有效。正是出于这个原因,Django 提供了一个 count() 方法。

      列表()。通过在其上调用 list() 来强制评估 QuerySet。为了 示例:

      entry_list = list(Entry.objects.all())
      

      布尔()。在布尔上下文中测试 QuerySet,例如使用 bool(), or, and or if 语句将导致查询被执行。如果 至少有一个结果,QuerySet 为 True,否则为 False。 例如:

      if Entry.objects.filter(headline="Test"):
         print("There is at least one Entry with the headline Test")
      

      注意:如果你只想确定是否存在至少一个结果(并且不需要实际的对象),使用 exists() 会更有效。

      【讨论】:

        猜你喜欢
        • 2013-12-20
        • 2020-03-28
        • 2012-02-22
        • 2010-11-22
        • 2014-10-30
        • 1970-01-01
        • 2021-05-30
        • 2010-12-23
        • 2012-08-16
        相关资源
        最近更新 更多