微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

如何解决Django ORM:误导“prefetch_related”的“first()”

在 DRF 端点上工作时,我遇到了在预取集上调用 prefetch_relatedfirst 的问题。让我们考虑两个模型:XYY 包含 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 不使用预取缓存,或者您能给我一个如何更清楚地处理它的提示吗? (我不想添加包装器来处理这个问题,我更喜欢原生 django orm 解决方案。另外我不能只从循环中取出第一个元素 - 我简化了这个例子)

提前致谢!

解决方法

.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() 不会简单地使用预取对象的原因相同。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。