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查询。
核心优化方案:重写视图查询集,结合annotate和精准prefetch_related
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
相关产品推荐
相关产品推荐

