You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过链式prefetch_related()减少Django的SQL查询次数?

优化Django中prefetch_related的SQL查询次数

问题核心
你在使用prefetch_related关联多表时,发现仍生成多条SQL,甚至直接单独查询各模型的SQL次数更少,需要在保留prefetch_related的前提下减少查询次数。

原因分析

  1. 先全局prefetch再获取单个对象:当你先执行Item.objects.prefetch_related(...).get(pk=pk),或者先获取全量QuerySet再get(pk=4),Django会先查询所有Item,再单独查询目标Item及其关联数据,属于重复查询。
  2. .values()绕过预取缓存:item.item_photo.values()这类写法会忽略prefetch缓存,触发新的SQL查询——因为.values()会直接构造新的查询集,不会使用已预取的对象。
  3. 关联关系类型不匹配:ItemStock如果实际是和Item一对一(你用了get(item_id=pk)),却用ForeignKey+prefetch_related组合,应该改用OneToOneField+select_related,后者通过SQL JOIN一次性获取主表和关联表数据。

解决方案

  • 先过滤目标Item再执行prefetch_related
    针对单个Item的场景,先通过filter(pk=pk)定位目标,再预取关联数据,这样prefetch只会针对该Item的关联表查询,避免全局查询后再单独获取:
pk = 4
# 先过滤再prefetch,仅查询目标Item及其关联数据
item = Item.objects.filter(pk=pk).prefetch_related(
    'item_photo', 'item_stock', 'item_review'
).first()

# 直接使用预取的关联对象,避免用.values()
context = {
    'form': form,
    'item': item,
    'photos': item.item_photo.all(),  # 使用预取的QuerySet
    'stock': item.item_stock.first(),  # 若为一对一关系,建议后续调整模型
    'review': item.item_review.all(),
}

这样只会生成4条SQL:1条查询Item,3条分别查询三个关联表(IN条件仅包含当前pk),和你单独查询的次数一致,但保留了ORM的关联对象操作便捷性。

  • 避免使用.values(),手动提取所需数据
    .values()会创建新查询集,不使用prefetch_related缓存的对象。如果需要字典格式数据,可以手动从预取对象中提取:
# 替代item.item_photo.values()的写法
photos = [{'id': p.id, 'photo': p.photo.url, 'priority': p.priority} for p in item.item_photo.all()]
  • 调整一对一关联的模型设计(针对ItemStock)
    如果每个Item对应唯一的ItemStock,将ItemStock的ForeignKey改为OneToOneField,然后用select_related代替prefetch_related,通过SQL JOIN一次性获取数据:
# 修改models.py中的ItemStock类
class ItemStock(models.Model):
    item = models.OneToOneField(Item, on_delete=models.SET_NULL,
        null=True, related_name="item_stock", verbose_name="商品番号")
    stock = models.IntegerField("在庫数", blank=False, default=0)

# 查询时组合使用select_related和prefetch_related
item = Item.objects.filter(pk=pk).select_related('item_stock').prefetch_related(
    'item_photo', 'item_review'
).first()

这样SQL次数减少到3条:1条Join查询Item和ItemStock,2条分别查询item_photo和item_review。

  • 正确结合过滤使用Prefetch类
    如果需要对关联表做自定义过滤,使用Prefetch类时也要先过滤主表,避免全局预取:
from django.db.models import Prefetch

pk = 4
item = Item.objects.filter(pk=pk).prefetch_related(
    Prefetch('item_photo', queryset=ItemPhoto.objects.order_by('priority'), to_attr='ipr'),
    Prefetch('item_stock', queryset=ItemStock.objects.all(), to_attr='isr'),
    Prefetch('item_review', queryset=ItemReview.objects.all(), to_attr='irr'),
).first()

# 使用预取的to_attr属性
context = {
    'form': form,
    'item': item,
    'photos': item.ipr,
    'stock': item.isr[0] if item.isr else None,
    'review': item.irr,
}

验证效果
调整后,针对单个Item的查询会生成:

  • 1条查询Item的SQL
  • 3条查询关联表的SQL(若用select_related处理ItemStock,则是2条)
    和你单独查询的次数一致,同时保留了ORM的关联对象操作,避免手动维护外键关联逻辑。

内容的提问来源于stack exchange,提问作者K-Yuuma

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 05:42:02