【问题标题】:Avoiding multiple references to the same object in Django ORM避免在 Django ORM 中多次引用同一对象
【发布时间】:2012-01-18 17:02:54
【问题描述】:

我们有一个应用程序具有高度相关的数据,即在很多情况下,两个对象可能通过关系引用同一个对象。据我所知,如果您尝试通过不同的、以前未评估的关系获取已获取对象的引用,Django 不会尝试返回对它的引用。

例如:

class Customer( Model ):
    firstName = CharField( max_length = 64 )
    lastName = CharField( max_length = 64 )

class Order( Model ):
    customer = ForeignKey( Customer, related_name = "orders" )

然后假设我们有一个客户在数据库中有两个订单:

order1, order2 = Order.objects.all()
print order1.customer # (1) One DB fetch here
print order2.customer # (2) Another DB fetch here
print order1.customer == order2.customer # (3) True, because PKs match
print id( order1.customer ) == id( order2.customer ) # (4) False, not the same object

当您拥有高度相关的数据时,访问对象关系的程度会导致数据库重复查询相同数据的程度增加并成为问题。

我们还为 iOS 编程,CoreData 的优点之一是它维护 context,因此在给定的上下文中,给定模型只有一个实例。在上面给出的示例中,CoreData 不会在 (2) 处进行第二次提取,因为它会使用内存中已经存在的客户来解析关系。

即使第 (2) 行被替换为旨在强制另一个数据库提取的虚假示例(如 print Order.objects.exclude( pk = order1.pk ).get( customer = order1.customer )),CoreData 也会意识到第二次提取的结果解析为内存中的模型并返回现有模型一个新的(即 (4) 将在 CoreData 中打印 True,因为它们实际上是同一个对象)。

为了对冲 Django 的这种行为,我们编写了所有这些可怕的东西,试图通过它们的 (type, pk) 将模型缓存在内存中,然后检查与 _id 后缀的关系,以尝试将它们从缓存中拉出盲目地用另一个 fetch 击中数据库。这会降低数据库吞吐量,但如果通过属性的正常关系查找意外发生在我们无法控制的某些 contrib 框架或中间件中,则感觉非常脆弱并且可能会导致问题。

Django 是否有任何最佳实践或框架来帮助避免这个问题?有没有人尝试在 Django 的 ORM 中安装某种线程本地上下文以避免重复查找并将多个内存中实例映射到同一个数据库模型?

我知道像 JohnnyCache 这样的查询缓存已经存在(并有助于降低数据库吞吐量),但是即使采取了这些措施,仍然存在多个实例映射到同一底层模型的问题。

【问题讨论】:

    标签: python django orm


    【解决方案1】:

    David Cramer 的 django-id-mapper 是这样做的一种尝试。

    【讨论】:

    • 谢谢 Daniel - 它已经很长时间没有更新了,但我会试一试,看看它是否仍然有效。
    • 所以经过一些研究,看起来这个东西可以工作,因为它基本上重新挂钩元类 __call__ 以在您获得 (type, pk) 缓存时返回缓存实例而不是新实例命中。不过,这仍然完全依赖于查询缓存,因为它不提供 ForeignKey 的子类,这些子类知道如何在触发真正的查询之前返回缓存的实例 - 所以不是 100% 理想的。可能会在 github fork 中实现这个 ForeignKey 并在此处返回结果。
    【解决方案2】:

    在 django 文档中有一个相关的DB optimization page;基本上可调用对象不会被缓存,但属性会被缓存(对order1.customer 的后续调用不会访问数据库),尽管仅在其对象所有者的上下文中(因此,不会在不同订单之间共享)。

    使用缓存

    正如您所说,解决问题的一种方法是使用数据库缓存。我们使用bitbucket的johnny cache,几乎是完全透明的;另一个好的透明的是 Mozilla 的cache machine。 您还可以选择不太透明的缓存系统,这些系统实际上可能更符合要求,请参阅djangopackages/caching

    如果不同的请求需要重用同一个客户,添加缓存确实是非常有益的;但请read this 至极适用于大多数透明缓存系统,以考虑您的写入/读取模式是否适合这样的缓存系统。

    优化请求

    您的精确示例的另一种方法是使用select_related

    order1, order2 = Order.objects.all().select_related('customer')
    

    这样Customer对象将在同一个sql请求中立即加载,成本很低(除非它是一个非常大的记录),并且不需要尝试其他包。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多