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

django-oscar-api ProductSerializer查询量激增,寻求优化方案

解决Django-Oscar产品API查询量暴增的优化方案

问题根源

你的legacy_title属性通过self.attr.legacy_title访问Oscar的属性存储系统,默认情况下每个产品访问attr都会触发单独的SQL查询(N+1问题)。虽然你用了prefetch_related("attributes"),但Oscar的AttributeAccessor(即self.attr)并未利用预取缓存,依然会发起单独查询。另外children、recommendations、images这些关联字段如果没有预取,也会各自产生N+1查询。

1. 用annotate直接注入legacy_title到主查询

把legacy_title的查询合并到产品主SQL中,彻底避免每个产品单独访问属性表:

from django.db.models import Case, When, F, CharField
from oscarapi.views.product import ProductList as CoreProductList
from oscar.apps.catalogue.models import ProductAttributeValue
from django.db.models import Prefetch

class ProductList(CoreProductList):
    def get_queryset(self):
        # 仅预取需要的legacy_title属性,减少不必要的数据加载
        legacy_attr_prefetch = Prefetch(
            'attributes',
            queryset=ProductAttributeValue.objects.filter(attribute__code='legacy_title'),
            to_attr='prefetched_legacy_attr'
        )
        
        return super().get_queryset()\
            .annotate(
                # 优先取属性表中的legacy_title,无值则用产品本身的title
                legacy_title=Case(
                    When(
                        attributes__attribute__code='legacy_title',
                        then='attributes__value_text'
                    ),
                    default=F('title'),
                    output_field=CharField()
                )
            )\
            .prefetch_related(
                legacy_attr_prefetch,
                'children',       # 批量预取子产品
                'recommendations',# 批量预取推荐产品
                'images'          # 批量预取产品图片
            )

2. 调整序列化器(可选)

直接在序列化器中映射注解生成的字段,无需依赖模型的属性方法,效率更高:

from rest_framework import serializers
from oscarapi.serializers.product import ProductSerializer as CoreProductSerializer

class ProductSerializer(CoreProductSerializer):
    # 直接使用annotate注入的字段
    legacy_title = serializers.CharField(read_only=True)
    
    class Meta(CoreProductSerializer.Meta):
        fields = (
            "url",
            "id",
            "description",
            "slug",
            "upc",
            "title",
            "structure",
            "legacy_title",
        )

如果要保留模型的legacy_title属性,可以修改它优先使用注解值,避免触发额外查询:

class Product(AbstractProduct):
    @property
    def legacy_title(self) -> str:
        # 优先用annotate注入的字段,无值再回退到属性访问
        if hasattr(self, 'legacy_title'):
            return self.legacy_title
        return attribute_error_as_none(lambda: self.attr.legacy_title) or self.title

关键优化点说明

  • annotate替代属性访问:将legacy_title的查询合并到主SQL中,彻底消除该字段的N+1问题。
  • 精准Prefetch:只预取需要的legacy_title属性,而非所有产品属性,减少数据传输和内存占用。
  • 批量预取关联字段:children、recommendations、images通过prefetch_related一次性加载,每个关联仅触发1次查询。

验证优化效果

使用django-debug-toolbar查看API请求的SQL查询数,优化后21个产品的查询数应该能控制在10次以内。

内容的提问来源于stack exchange,提问作者Kevin Renskers

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 00:50:00