【问题标题】:django many-to-many field: prefetch primary keys onlydjango 多对多字段:仅预取主键
【发布时间】:2012-05-03 15:55:02
【问题描述】:

我正在尝试优化 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 数据结构中,其中Things 使用它们的ids(主键)引用Labels .像这样的:

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

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

方法一:

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) 需要单独获取每个 ThingLabels。

方法二:

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

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

方法三:

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

总结:

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

【问题讨论】:

  • 也许尝试使用m2m的中间模型? DB 方案和其他任何东西都将保持不变,但您只能fetch_related 这个模型并从中获取标签的 ID。如果您将它与 through 参数链接到 M2M,则某些方法(如 add())将被破坏,但您可以手动为其提供 db_table 并且不要触摸 m2m 字段,所以它应该可以工作。
  • 感谢@ilvar,您的评论让我得到了下面的答案。

标签: python database django optimization django-queryset


【解决方案1】:

我明白了。感谢 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):

如果我的Labels 比Things 多,这将导致查询具有更小的IN 子句。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-04-27
    • 2011-02-05
    • 2023-03-16
    • 2021-11-20
    • 2016-02-23
    • 2020-10-28
    相关资源
    最近更新 更多