Django prefetch_lated 似乎没有按预期工作

问题描述 投票:0回答:2

说明

有一个

Foo
模型有一个外键字段
bar
:

class Foo(models.Model):
  ...
  bar = models.ManyToManyField('bar.Bar', related_name='some_bar')
  ...

此外

Foo
get_config()
方法,该方法返回其字段,包括
bar
,例如:

def get_config(self):
  return {
    ...
    'bar': map(lambda x: x.get_config(), self.bar.all())
    ...

现在数据库中有 10,000 行

Foo
。还有一些
Bar
行。

尝试检索大约 10,000 个

Foo
的数据,包括嵌套的
Bar
数据:

query = Foo.objects.all().prefetch_related('bar')
return [obj.get_config() for obj in query]

问题

查询执行大约 6 秒。如果没有

bar
字段 - 只有 400 毫秒。

预取似乎无法完全工作

bar.get_config()
似乎每个迭代步骤都会访问数据库。它应该简单地加载所有
Bar
对象一次,并从该栏查询获取配置以填充每个
foo
配置。

想法

如果在

iterator()
循环中使用
for
,则调用几乎会停滞并花费数十秒:
[obj.get_config() for obj in query.iterator()]

django django-models django-queryset
2个回答
0
投票

prefetch_related
正在工作。 Django 使用包含所有
Bar
中的
ids
的 WHERE 子句预取
Foo
。这个具有 10k id 的 SELECT 比 10k SELECTS 快得多,但比没有任何过滤器的单个 SELECT 慢得多。


0
投票

虽然我还没有测试过它,但我怀疑您在查询中看到的大部分缓慢来自 python,而不是数据库查询。使用

prefetch_related(...)
时,会对所有 Foo 进行 1 次查询,对所有 Bar 进行 1 次查询。然后,Python 领域的 Django 将这些查询的结果拼接在一起。

它们的性能可能会根据您的设置细节(数据库引擎、索引、数据库主机规格、服务主机规格等)而有所不同。您需要进行一些分析才能深入了解您的瓶颈。


关于您在“想法”标题下提到的行为...Django 版本<4.1 在使用

prefetch_related
时忽略
.iterator()

请注意,如果您使用 iterator() 运行查询,则 prefetch_lated() 调用将被忽略,因为这两个优化一起没有意义。

在 Django 4.1

添加了对混合
prefetch_related
iterator 的支持。如果您升级到 Django >=4.1,那么您可以再次尝试使用
.iterator()
。这可能不会提高您的总数据库时间,但可能会提高内存使用率,并避免服务主机瓶颈。

© www.soinside.com 2019 - 2024. All rights reserved.