预取 manytomany 字段不会改变执行速度

Prefetching manytomany field doesn't change execution speed

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)) 每个周期都会访问数据库。

为什么会这样,我如何强制从 manytomany 字段中预取?

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

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

Why is this and how can I force to prefetch from a manytomany field?

发生这种情况的原因是因为您进行了与 manyToManyField.all() 不同的查询,因此 未执行 。想象一下,如果您 myManyToManyField.filter(some_col=some_val),那么它也会访问数据库,因为数据库经过优化可以有效过滤。

如果要获取值,请使用:

# no extra query

for mm in m:
    print(list(mm.manyToManyField<b>.all()</b>))

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

# no extra query

for mm in m:
    print([<b>k.id</b> for k in mm.manyToManyField.all()])

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

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

from django.db.models import <b>Prefetch</b>

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

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