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

Python 避免在Django ORM中多次引用同一对象,python,django,orm,Python,Django,Orm,我们有一个数据高度相关的应用程序,也就是说,在很多情况下,两个对象可能通过关系引用同一个对象。据我所知,如果您试图通过一个不同的、以前未计算的关系获取一个已经获取的对象,Django不会尝试返回对该对象的引用 例如: class Customer( Model ): firstName = CharField( max_length = 64 ) lastName = CharField( max_length = 64 ) class Order( Model ):

我们有一个数据高度相关的应用程序,也就是说,在很多情况下,两个对象可能通过关系引用同一个对象。据我所知,如果您试图通过一个不同的、以前未计算的关系获取一个已经获取的对象,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)行被替换为一个虚假的示例,该示例旨在强制另一个DB获取(如
print Order.objects.exclude(pk=order1.pk).get(customer=order1.customer)
),CoreData也会意识到第二次获取的结果解析为内存中的模型,并返回现有模型而不是新模型(即(4)将在CoreData中打印True,因为它们实际上是同一个对象)

为了避免Django的这种行为,我们写了这么多可怕的东西,试图通过
(type,pk)
将模型缓存在内存中,然后检查与
\u id
后缀的关系,试图将它们从缓存中拉出来,然后用另一个获取盲目地命中数据库。这会降低数据库吞吐量,但感觉非常脆弱,如果在我们无法控制的contrib框架或中间件中意外发生通过属性的正常关系查找,则可能会导致问题

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

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

David Cramer就是这样做的一个尝试。

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

使用缓存

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

如果不同的请求需要重复使用同一客户,那么添加缓存确实非常有益;但请考虑一下,它适用于大多数透明的缓存系统,您的写/读模式是否适合这种缓存系统

优化请求

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


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

谢谢Daniel-它已经很长时间没有更新了,但我会尝试一下,看看它是否仍然有效。因此,经过一点研究,看起来这个东西可以工作,因为它基本上会重新钩住元类
\uuuuu call\uuuu
,以在获得(type,pk)缓存命中时返回缓存实例,而不是新实例。不过,这仍然完全依赖于查询缓存,因为它没有提供ForeignKey的子类,这些子类在触发真正的查询之前知道如何返回缓存实例,所以不是100%理想。可能会在github fork中实现这个ForeignKey,并将结果发布到这里。
order1, order2 = Order.objects.all().select_related('customer')