django多对多字段:仅预取主键

时间:2012-04-23 01:23:56

标签: python database django optimization django-queryset

我正在尝试优化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)

我有一个函数可以获取所有LabelThing并将它们放入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 子句,但仍然不能令人满意,因为如果INprefetch_related('thing_set')ThingThing会抓取重复的Label s

要点:

LabelThingManyToManyField连接。无论如何,我正在提取所有 LabelThing。那么我如何有效地获取他们的多对多关系呢?

1 个答案:

答案 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-toolbardebugsqlshell管理命令非常适合实际查看一段代码所产生的查询。