避免在Django ORM中多次引用同一对象

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

例如:

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

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

然后假设我们有一个在DB中有两个订单的客户:

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的好处之一是它维护上下文,以便在给定的上下文中只有给定模型的一个实例。 在上面给出的例子中,CoreData不会在(2)处完成第二次获取,因为它会使用已经在内存中的客户解决关系。

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

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

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

我知道像JohnnyCache这样的查询缓存类的东西已经存在了(并且有助于减少数据库的吞吐量),但即使采取了这些措施,仍然存在将多个实例映射到相同的基础模型的问题。


大卫克莱默的django-id-mapper是这样做的一个尝试。


django文档中有一个相关的数据库优化页面; 基本上可调用对象不被缓存,但属性是(后续对order1.customer调用不会触及数据库),尽管只是在对象所有者的上下文中(所以不在不同命令之间共享)。

使用缓存

正如你所说,解决你的问题的一种方法是使用数据库缓存。 我们使用bitbucket的johnny cache,它几乎是完全透明的; 另一个好透明的是Mozilla的缓存机器。 您也可以选择透明度较低的缓存系统,但实际上可能更适合该账单,请参阅djangopackages / caching。

如果不同的请求需要重新使用同一个客户,那么添加一个缓存确实是非常有益的; 但是请阅读这个适用于大多数透明缓存系统的思路,以便您的Write / Read模式适合这种缓存系统。

优化请求

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

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

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

链接地址: http://www.djcxy.com/p/10331.html

上一篇: Avoiding multiple references to the same object in Django ORM

下一篇: Can someone explain clearly how jQuery.when() and deferred.then() works?