8

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

我有一个函数可以获取所有Labels 和Things 并将它们放入 JSON 数据结构中,其中s 使用它们的 s (主键)Thing引用s 。像这样的东西:Labelid

{
    'labels': [
        { 'id': 123, 'name': 'label foo' },
        ...
    ],
    'things': [
        { 'id': 45, 'name': 'thing bar', 'labels': [ 123, ... ] },
        ...
    ]
}

使用 Django 获取这种数据结构的最有效方法是什么?假设我有L Label s 和T Thing s,平均值Thingx Label s。

方法一:

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单独获取 s。

方法二:

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) 次数据库查询,因为获取的s 现在在单个附加查询中预取了Thing它们的 s。Label

这仍然不能令人满意。 prefetch_related('labels')将获取相同的许多副本Label,而我只需要它们id的 s。有没有办法只预取ids 的Labels ?我试过prefetch_related('labels__id')了,但没有用。我还担心因为T很大(数百个),prefetch_related('labels')导致 SQL 查询带有很大的IN子句。L要小得多(< 10),所以我可以这样做:

方法三:

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子句,但仍然不能令人满意,因为如果 a有多个s,则会prefetch_related('thing_set')获取重复的s。ThingThingLabel

概括:

Label并由Thinga 连接ManyToManyField。无论如何,我正在获取所有 Labels 和Things 。那么我如何有效地获取他们的多对多关系呢?

4

1 回答 1

9

我知道了。感谢 ilvar,他对问题的评论将我指向了through表格

如果您未指定显式直通模型,则仍有一个隐式直通模型类可用于直接访问为保存关联而创建的表。它具有三个字段来链接模型。

长话短说:

# 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 多于Things,这将导致查询的IN子句更小。

Django-debug-toolbardebugsqlshell管理命令非常适合实际查看一段代码正在执行的查询。

于 2012-04-24T02:09:13.473 回答