Django ViewSet数据库查询优化求助:反向关联下prefetch_related失效
优化DRF反向关联与深层多对多查询的性能方案
问题根源
你当前的核心问题有两个:
- 反向关联的
collection_set在序列化阶段才做过滤(FilteredCollectionSerializer),导致无法直接用普通prefetch_related预取有效数据,还会加载大量无用的Collection记录。 - 深层关联的
card__tag、card__system_tags以及collection__system_tags都没有预取,触发了大量N+1查询。
解决方案:嵌套Prefetch预取所有关联数据
通过Django的Prefetch对象,我们可以在数据库层面完成反向关联的过滤,同时嵌套预取所有深层关联的多对多数据,彻底消除额外查询。
1. 修改ViewSet的查询集
在SectionViewset的get_queryset中,使用嵌套的Prefetch一次性预取所有需要的数据:
from django.db.models import Prefetch from datetime import datetime class SectionViewset(viewsets.ModelViewSet): serializer_class = serializers.SectionSerializer http_method_names = ['get'] def get_queryset(self): now = datetime.now() # 1. 定义过滤后的Collection查询集,同时预取其关联的card、system_tags filtered_collections = Collection.objects.filter( collection_status=True, collection_expiredate__gte=now ).prefetch_related( # 2. 预取Card,并同时预取Card的tag和system_tags Prefetch( 'card', queryset=Card.objects.prefetch_related('tag', 'system_tags') ), # 3. 预取Collection自身的system_tags 'system_tags' ) # 4. 预取Section关联的过滤后的Collection集合 queryset = Section.objects.filter( section_status=True, section_expiredate__gte=now ).prefetch_related( Prefetch('collection_set', queryset=filtered_collections) ) return queryset
2. 可选:调整Serializer的过滤逻辑
由于已经在数据库层面完成了Collection的过滤,你可以移除FilteredCollectionSerializer,简化CollectionSerializer的配置:
class CollectionSerializer(serializers.ModelSerializer): card = CardSerializer(many=True, read_only=True) system_tags = AssetTagBadgeSerializer(many=True, read_only=True) class Meta: model = Collection fields = ('id', 'card', 'system_tags')
如果必须保留序列化阶段的过滤(比如有额外的业务逻辑),也可以继续使用FilteredCollectionSerializer,此时它只会过滤已经预取的有效数据,不会触发新的数据库查询。
优化原理
- 数据库层面过滤:通过
Prefetch指定反向关联的查询集,避免加载无用的Collection记录,减少数据传输和内存占用。 - 嵌套预取:一次性预取所有层级的关联数据(Section→Collection→Card→Tag/SystemTag、Collection→SystemTag),将原本的N+1查询缩减为固定的几次查询。
内容的提问来源于stack exchange,提问作者ichthyocentaurs
相关产品推荐
相关产品推荐

