Django REST Framework:如何对单个实例使用prefetch_related
解决单个实例传入Serializer时的N+1查询问题
核心思路
Django支持对单个实例预取关联数据,通过查询集预取规则或手动补预取的方式,结合Serializer内关联数据访问逻辑的优化,就能彻底消除N+1查询。
步骤1:预取关联数据
方案A:获取实例时直接完成预取(推荐)
在获取单个Workout实例的查询阶段,就通过prefetch_related和Prefetch对象把后续需要用到的关联数据一次性取出:
from django.db.models import Prefetch from .models import Workout, WorkoutExercises # 预取exercises、关联的WorkoutExercises及嵌套的Rule instance = queryset.prefetch_related( 'exercises', Prefetch( 'workoutexercises_set', queryset=WorkoutExercises.objects.select_related('rule') ) ).order_by('?').first() workout = WorkoutModelSerializer(instance) return Response(workout.data, status=status.HTTP_200_OK)
方案B:对已存在的单个实例补做预取
如果已经拿到未预取关联数据的实例,可通过prefetch_related_objects手动添加预取:
from django.db.models import prefetch_related_objects, Prefetch from .models import WorkoutExercises # 给已有的Workout实例补预取关联数据 prefetch_related_objects( [instance], 'exercises', Prefetch( 'workoutexercises_set', queryset=WorkoutExercises.objects.select_related('rule') ) )
步骤2:优化Serializer内的查询逻辑
原Serializer中get_exercise_group的循环+filter写法会触发额外查询,改为直接遍历预取好的workoutexercises_set,同时修正get_calorie中的错误用法:
class WorkoutModelSerializer(serializers.ModelSerializer): calorie = SerializerMethodField(help_text='calorie') def get_calorie(self, obj): # 去掉defer('calorie')——需要该字段却延迟加载会触发额外查询 return sum(exercise.calorie for exercise in obj.exercises.all()) exercise_group = SerializerMethodField() def get_exercise_group(self, obj): exercise_groups = [] # 直接遍历预取的workoutexercises_set,无需再执行filter查询 for workout_exercise in obj.workoutexercises_set.all(): exercise = workout_exercise.exercise group_name = workout_exercise.rule.group_name # 这里添加你的业务处理逻辑 exercise_groups.append({ 'exercise_name': exercise.name, 'group_name': group_name # 其他需要返回的字段... }) return exercise_groups class Meta: model = Workout fields = ['id', 'calorie', 'exercise_group'] # 根据实际需求调整字段
关键注意点
Prefetch对象可以精准控制预取的查询集,比如用select_related('rule')把关联的Rule数据一并取出,避免访问rule字段时的N+1。- 原
get_calorie中的defer('calorie')是错误操作:需要获取该字段却延迟加载,会导致每个exercise都触发一次单独查询,必须移除。 - 直接遍历
workoutexercises_set而非通过exercises反向查询关联表,能充分利用预取的数据,彻底消除重复查询。
内容的提问来源于stack exchange,提问作者Tony.Luo
相关产品推荐
相关产品推荐

