django - Django ORM:误导`prefetch_related`的`first()`
问题描述
在 DRF 端点上工作时,我遇到了一个问题prefetch_related
并first
调用了预取集。让我们考虑两个模型:X
和Y
; 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
不使用预取缓存或者你能给我一个提示如何更清楚地处理它?(我不想添加包装器来处理它,我更喜欢原生 django orm 解决方案。我也不能只从循环中获取第一个元素 - 我简化了很多示例)
提前致谢!
解决方案
.first()
基本上使用LIMIT
SQL 中的子句来获取查询的第一个对象。因此,当有人调用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()
不会简单地使用预取对象的原因相同。
推荐阅读
- php - Magento2 CE 不能使用模型自定义扩展属性
- go - 通过 pkg.go.dev 禁用 go 模块缓存
- javascript - 关闭浏览器时会话无效,除 IE 外,其他浏览器不工作
- high-availability - Axon 服务器高可用性
- java - 本地主机 MySQL 数据库和 Java .jar 创建
- android - 无法在片段上绘制 MapView
- android - alertDialog.show 在片段中不起作用
- python - 如何使用烧瓶 Jinja 将数据从数据框传递到 chart.js
- rust - 重塑 Vec 或数组
- python - Python Bloomberg API xbbg:“NoneType”对象没有属性“值”