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

时间:2012-01-18 17:02:54

标签: python 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的一个好处是它维护 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中安装某种线程局部上下文,以避免重复查找并将多个内存实例映射到同一个DB模型?

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

2 个答案:

答案 0 :(得分:2)

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

答案 1 :(得分:1)

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

使用缓存

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

如果不同的请求需要重新使用同一个客户,那么添加缓存确实非常有用;但请{@ 3}}适用于大多数透明缓存系统,以便在您的写/读模式适合这种缓存系统时进行思考。

优化请求

您的确切示例的另一种方法是使用read this

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

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