Django ORM:误导“prefetch_related”的“first()”

xesrikrc  于 2023-02-17  发布在  Go
关注(0)|答案(1)|浏览(175)

在处理DRF端点时,我遇到了prefetch_relatedfirst在预取集上调用的问题。XY;Y包含指向X的外键。
然后执行以下代码:

qs = X.objects.all().prefetch_related("y_set")

for x in qs:
    for y in x.y_set.all():
        print(e)

一切正常,django按预期执行了2个查询。
然后我表演:

for x in qs:
    for y in x.y_set.all():
        print(e)
    first = x.y_set.first()

在这个例子中,Django执行了n+2个出乎意料的查询(至少对我来说)。
我找到了一个变通方案:

for x in qs:
    for y in x.y_set.all():
        print(e)
    first = y_set.all()[0] if y_set.all() else None

但我并不满意-我觉得检查qs是否不为空,然后获取第一个元素有点麻烦,我肯定更喜欢使用first或其他隐藏此逻辑的函数。
有人能解释一下为什么first不使用预取缓存吗?或者你能给予我一个提示,让我更清楚地处理它吗?(我不想添加一个 Package 器来处理这个问题,我更喜欢原生的django orm解决方案。另外,我不能只从循环中取出第一个元素-我简化了很多例子)

von4xj4u

von4xj4u1#

.first()基本上使用SQL中的LIMIT子句来获取查询的第一个对象,因此当调用queryset.first()时,它自然会进行单独的查询。
你会进一步问,既然查询集已经存在于内存中,为什么.first()不直接使用求值查询集呢?
在一个查询集.annotate(...).filter(...)等上链接方法是很常见的,我们可以这样做:

queryset = SomeModel.objects.all()
for object in queryset:
    print(object)
queryset2 = list(queryset.filter(a=1))

这里我们期望queryset2对数据库进行一个不同的查询,而不是在python级别过滤对象,因为由于某种原因,数据库本身可能有新的条目,或者我们甚至可能做一些注解,而不是简单地调用.filter(),所以我们希望这是一个单独的查询。这本质上与.first()不会简单地使用预取对象的原因相同。

相关问题