【问题标题】:Prefetching manytomany field doesn't change execution speed预取多线程字段不会改变执行速度
【发布时间】:2024-04-15 02:35:01
【问题描述】:
m = MyModel.objects.all().only("colA", "colB").prefetch_related("manyToManyField")

for mm in m:
    print(mm.id)
    list(mm.manyToManyField.values_list('id', flat=True))

此代码执行时间过长。

这几乎不需要时间(循环中没有引用 manyToManyField):

m = MyModel.objects.all().only("colA", "colB").prefetch_related("manyToManyField")

for mm in m:
    print(mm.id)

这几乎与第一次精确相同

m = MyModel.objects.all().only("colA", "colB")

for mm in m:
    print(mm.id)
    list(mm.manyToManyField.values_list('id', flat=True))

这让我觉得.prefetch_related("manyToManyField") 没用,它实际上并没有获取任何东西,list(mm.manyToManyField.values_list('id', flat=True)) 在每个周期都会访问数据库。

为什么会这样?如何强制从多对多字段中预取?

我尝试删除 list(),但随后 mm.manyToManyField.all().values_list 给了我一个不是 JSON 可序列化的查询集(不,我不想安装 rest 框架)。

还尝试了list(mm.manyToManyField.all().values_list)list():仍然非常缓慢。

【问题讨论】:

  • 使用.values_list确实不会提高性能,因为所有不同于.all()的查询都会再次访问数据库。
  • 也试过list(manyToManyField.all().values_list(...)),还是一样的问题。现在更新问题...
  • 当然,因为您再次进行了与.prefetch_related(..) 的查询不同的查询,因此它必须对数据库进行另一次查询。
  • 这是一个不同的查询?我正在查询 mm 对象,它应该预先加载了 m2m 字段。
  • 哦,你的意思是我应该只调用 .all() 然后手动做值列表,而不是使用 Django 函数做列表?

标签: python django django-orm


【解决方案1】:

为什么会这样?如何强制从多对多字段中预取?

发生这种情况的原因是您进行了与manyToManyField.all() 不同的查询,因此该查询没有执行。想象一下,你会myManyToManyField.filter(some_col=some_val),然后它也会命中数据库,因为数据库已经过优化,可以有效过滤。

如果您要获取值,请使用:

# no extra query

for mm in m:
    print(list(mm.manyToManyField.all()))

或者如果你想打印主键,你可以用列表理解来获取这些,例如:

# no extra query

for mm in m:
    print([k.id for k in mm.manyToManyField.all()])

它不会进行额外的查询,因为您已经使用.prefetch_related('manyToManyField') 加载了该查询,但所有变体(如过滤、注释等)都没有加载。

但是,您可以使用Prefetch objects [Django-doc] 传递任意查询集以进行预取。例如,如果您想检索 .values_list('id'),您可以使用以下命令预取:

from django.db.models import Prefetch

m = MyModel.objects.only("colA", "colB").prefetch_related(
    Prefetch(
        'myManyToManyField',
        queryset=TargetModel.objects.filter(pk__gt=5),
        to_attr='filtered_pks'
    )
)

那么由此产生的MyModels 将在这里有一个额外的属性'filtered_pks',其中包含该相关模型的.filter(pk__gt=5)。因此TargetModel 就是ManyToManyField 所指的模型。

【讨论】:

  • 对 .all() 进行了更简单的手动循环,但是将查询集传递给 Prefetch 对我来说是新事物,谢谢!
最近更新 更多