Django ORM:优化涉及多对多关系的查询
Posted
技术标签:
【中文标题】Django ORM:优化涉及多对多关系的查询【英文标题】:Django ORM: Optimizing queries involving many-to-many relations 【发布时间】:2010-12-11 03:44:37 【问题描述】:我的模型结构如下:
class Container(models.Model):
pass
class Generic(models.Model):
name = models.CharacterField(unique=True)
cont = models.ManyToManyField(Container, null=True)
# It is possible to have a Generic object not associated with any container,
# thats why null=True
class Specific1(Generic):
...
class Specific2(Generic):
...
...
class SpecificN(Generic):
...
比如说,我需要检索所有与特定 Container 有关系的Specific
-type 模型。
用于此的 SQL 或多或少是微不足道的,但这不是问题所在。不幸的是,我在使用 ORM(尤其是 Django 的 ORM)方面不是很有经验,所以我可能在这里遗漏了一个模式。
当以蛮力方式完成时,-
c = Container.objects.get(name='somename') # this gets me the container
items = c.generic_set.all()
# this gets me all Generic objects, that are related to the container
# Now what? I need to get to the actual Specific objects, so I need to somehow
# get the type of the underlying Specific object and get it
for item in items:
spec = getattr(item, item.get_my_specific_type())
这会导致大量的数据库命中(每个通用记录一个,与容器相关),所以这显然不是这样做的方法。现在,也许可以通过直接获取 SpecificX 对象来完成:
s = Specific1.objects.filter(cont__name='somename')
# This gets me all Specific1 objects for the specified container
...
# do it for every Specific type
这样,对于每种特定类型,数据库都会被命中一次(我猜是可以接受的)。
我知道,.select_related() 不适用于 m2m 关系,所以在这里没有太大帮助。
重申一下,最终结果必须是一组 SpecificX 对象(不是 Generic)。
【问题讨论】:
回想起来,这个问题现在对我来说似乎有点毫无意义,因为我已经提供了唯一可能的答案。毕竟,没有办法从具有任意字段的多个表中获得联合结果集。好的,显然,这是一种方法,但它很丑陋并且涉及动态 sql 和/或“select *”。我想。 您的问题实际上与优化多对多关系无关,而与针对多表继承优化查询有关。这确实是一个难题。 现在我想起来了,是什么让我相信,这个问题是关于 m2m 关系的,实际上,select_related 并不遍历多对多关系。 【参考方案1】:我认为您已经概述了两种简单的可能性。您可以针对 Generic 执行单个过滤器查询,然后将每个项目转换为其特定子类型(导致 n+1 个查询,其中 n 是返回的项目数),或者您对每个特定表进行单独查询(导致 k查询,其中 k 是特定类型的数量)。
实际上值得进行基准测试,看看哪些在现实中更快。第二个似乎更好,因为它(可能)查询更少,但是每个查询都必须与 m2m 中间表执行连接。在前一种情况下,您只执行一个连接查询,然后执行许多简单的查询。一些数据库后端在处理大量小查询时的性能要好于更少、更复杂的查询。
如果对于您的用例而言,第二个实际上明显更快,并且您愿意做一些额外的工作来清理您的代码,那么应该可以为“预取”的通用模型编写一个自定义管理器方法" 来自给定查询集的相关特定表的所有子类型数据,每个子类型表仅使用一个查询;类似于 this snippet 如何通过批量预取优化通用外键。这将为您提供与第二个选项相同的查询,使用第一个选项的 DRYer 语法。
【讨论】:
不知道是谁投了反对票。是的,我会研究编写自定义管理器的可能性,但是,正如我所说,我对 ORM 的经验非常有限(不过我对 SQL 没有任何问题),所以内部仍然有点对我来说是一个黑匣子。无论如何,我会去看看我能做什么。【参考方案2】:不是一个完整的答案,但您可以通过这样做避免大量点击
items= list(items)
for item in items:
spec = getattr(item, item.get_my_specific_type())
而不是这个:
for item in items:
spec = getattr(item, item.get_my_specific_type())
确实,通过强制转换为 python 列表,您可以强制 django orm 加载查询集中的所有元素。然后它在一个查询中执行此操作。
【讨论】:
这是一个很好的提示(在文档中提到,我读过:)。而且也不太明显。 嗯,这不是真的。在这种情况下,强制转换为列表没有区别。在这两个版本中,仅针对 Generic 表执行一次查询,在这两个版本中,针对每个项目针对 SpecificX 表执行一次查询。两者的查询数量相同。【参考方案3】:我无意中发现了以下帖子,它几乎回答了您的问题:
http://lazypython.blogspot.com/2008/11/timeline-view-in-django.html
【讨论】:
以上是关于Django ORM:优化涉及多对多关系的查询的主要内容,如果未能解决你的问题,请参考以下文章
如何在双连接关系之后在 Django 中执行查询(或:如何绕过 Django 对多对多“通过”模型的限制?)