我正在尝试优化Django应用的数据库查询。这是一个简化的例子:
class Label(models.Model):
name = models.CharField(max_length=200)
# ... many other fields ...
class Thing(models.Model):
name = models.CharField(max_length=200)
labels = models.ManyToManyField(Label)
我有一个函数可以获取所有Label
和Thing
并将它们放入JSON数据结构中,其中Thing
使用它们引用Label
s id
s(主键)。像这样:
{
'labels': [
{ 'id': 123, 'name': 'label foo' },
...
],
'things': [
{ 'id': 45, 'name': 'thing bar', 'labels': [ 123, ... ] },
...
]
}
使用Django获取此类数据结构的最有效方法是什么?假设我有 L Label
s和 T Thing
s,平均Thing
有 x Label
秒。
方法1:
data = {}
data['labels'] = [model_to_dict(label) for label in Label.objects.all()]
data['things'] = [model_to_dict(thing) for thing in Thing.objects.all()]
这使得(1 + 1 + T )数据库查询,因为model_to_dict(thing)
需要单独为每个Label
获取Thing
。
方法2:
data = {}
data['labels'] = [model_to_dict(label) for label in Label.objects.all()]
data['things'] = [model_to_dict(thing) for thing in
Thing.objects.prefetch_related('labels').all()]
这仅使(1 + 1 + 1)个数据库查询,因为现在提取的Thing
在一个附加查询中预取了Label
。
这仍然不能令人满意。 prefetch_related('labels')
会获取相同Label
的多个副本,而我只需要id
个。有没有办法只预取id
的{{1}} s?我试过了Label
但是没有用。我还担心因为 T 很大(数百),prefetch_related('labels__id')
会导致SQL查询带有大prefetch_related('labels')
子句。 L 要小得多(<10),所以我可以这样做:
方法3:
IN
这导致较小的data = {}
data['labels'] = [model_to_dict(label) for label in
Label.objects.prefetch_related('thing_set').all()]
things = list(Thing.objects.all())
# plug in label ids by hand, and also fetch things that have zero labels
# somehow
子句,但仍然不能令人满意,因为如果IN
有prefetch_related('thing_set')
个Thing
,Thing
会抓取重复的Label
s
要点:
Label
和Thing
由ManyToManyField
连接。无论如何,我正在提取所有 Label
和Thing
。那么我如何有效地获取他们的多对多关系呢?
答案 0 :(得分:8)
我明白了。感谢ilvar,他对这个问题的评论指向through
tables。
如果您没有指定显式直通模型,则仍然存在 通过模型类隐式,您可以使用它来直接访问表 创建以保持关联。它有三个字段来链接 模型。
长话短说:
# Fetch all labels and things:
labels = list(Label.objects.all())
things = list(Thing.objects.all())
# Fetch all label-thing pairs:
labels_of = defaultdict(lambda: [])
for pair in Thing.labels.through.objects.filter(label__in=labels):
labels_of[pair.thing_id].append(pair.label_id)
# Put everything together:
data = {}
data['labels'] = [model_to_dict(label) for label in labels]
data['things'] = []
for thing in things:
thing_dict = model_to_dict(thing, exclude='labels')
thing_dict['labels'] = labels_of[thing.id]
data['things'].append(thing_dict)
这会产生(1 + 1 + 1)个查询,并且不会重复获取任何内容。我也可以将第一个for循环更改为:
for pair in Thing.labels.through.objects.filter(thing__in=things):
如果我有Label
s而不是Thing
s,这将导致查询的IN
子句更小。
Django-debug-toolbar的debugsqlshell
管理命令非常适合实际查看一段代码所产生的查询。