django DRF预取相关仍导致N+1个查询

h9vpoimq  于 2023-08-08  发布在  Go
关注(0)|答案(1)|浏览(77)

型号:

class GradePolicy(models.Model):
    name = models.CharField(max_length=30)
    minScore = models.DecimalField(default=0, max_digits=4, decimal_places=1, db_column="minscore")
    maxScore = models.DecimalField(max_digits=4, decimal_places=1, default=100, db_column="maxscore")
    policyChoices = (
        ('Numerical', 'Numerical'),
        ('Textual', 'Textual')
    )
    type = models.CharField(max_length=30, default='Textual', choices=policyChoices)

class GradeLevel(models.Model):
    name = models.CharField(max_length=30)
    score = models.DecimalField(decimal_places=2, max_digits=5)
    abbreviation = models.CharField(max_length=4)
    policy = models.ForeignKey(GradePolicy, null=True, blank=True,
                           on_delete=models.DO_NOTHING)

字符串
浏览次数:

class GradePolicyViewSet(viewsets.ModelViewSet):
    """
    Retrieve grade policies
    """
    queryset = GradePolicy.objects.prefetch_related('gradelevel_set').order_by('pk')
    serializer_class = GradePolicySerializer

class GradeLevelViewSet(viewsets.ModelViewSet):
    queryset = GradeLevel.objects.all().order_by('pk')
    serializer_class = GradeLevelSerializer


序列化器:

class GradeLevelSerializer(serializers.HyperlinkedModelSerializer):
    url = serializers.HyperlinkedIdentityField(view_name="gbook:gradelevels-detail")
    policy = serializers.PrimaryKeyRelatedField(read_only=True)

    class Meta:
        model = GradeLevel
        fields = ['pk', 'url', 'name', 'score', 'abbreviation', 'policy']

class GradePolicySerializer(serializers.HyperlinkedModelSerializer):
    url = serializers.HyperlinkedIdentityField(view_name="gbook:gradepolicies-detail")
    levels = serializers.SerializerMethodField()

    def get_levels(self, obj):
        policy: GradePolicy = obj
        levels = policy.gradelevel_set.prefetch_related('policy').order_by("-score").all()
        return GradeLevelSerializer(levels, many=True, context={'request': None}).data

    class Meta:
        model = GradePolicy
        fields = ['pk', 'url', 'name', 'minScore', 'maxScore', 'type', 'levels']


我收到Sentry的报告称,调用/gradepoliciesGradePolicylist端点)会导致N+1个查询。看看这些查询,情况似乎确实如此:

(0.004) SELECT "gbook_gradepolicy"."id", "gbook_gradepolicy"."name", "gbook_gradepolicy"."minscore", "gbook_gradepolicy"."maxscore", "gbook_gradepolicy"."type" FROM "gbook_gradepolicy" ORDER BY "gbook_gradepolicy"."id" ASC; args=()

(0.005) SELECT "gbook_gradelevel"."id", "gbook_gradelevel"."name", "gbook_gradelevel"."score", "gbook_gradelevel"."abbreviation", "gbook_gradelevel"."policy_id" FROM "gbook_gradelevel" WHERE "gbook_gradelevel"."policy_id" = 2 ORDER BY "gbook_gradelevel"."score" DESC; args=(2,)

(0.001) SELECT "gbook_gradelevel"."id", "gbook_gradelevel"."name", "gbook_gradelevel"."score", "gbook_gradelevel"."abbreviation", "gbook_gradelevel"."policy_id" FROM "gbook_gradelevel" WHERE "gbook_gradelevel"."policy_id" = 3 ORDER BY "gbook_gradelevel"."score" DESC; args=(3,)

(0.001) SELECT "gbook_gradelevel"."id", "gbook_gradelevel"."name", "gbook_gradelevel"."score", "gbook_gradelevel"."abbreviation", "gbook_gradelevel"."policy_id" FROM "gbook_gradelevel" WHERE "gbook_gradelevel"."policy_id" = 4 ORDER BY "gbook_gradelevel"."score" DESC; args=(4,)

(0.001) SELECT "gbook_gradelevel"."id", "gbook_gradelevel"."name", "gbook_gradelevel"."score", "gbook_gradelevel"."abbreviation", "gbook_gradelevel"."policy_id" FROM "gbook_gradelevel" WHERE "gbook_gradelevel"."policy_id" = 5 ORDER BY "gbook_gradelevel"."score" DESC; args=(5,)

(0.001) SELECT "gbook_gradelevel"."id", "gbook_gradelevel"."name", "gbook_gradelevel"."score", "gbook_gradelevel"."abbreviation", "gbook_gradelevel"."policy_id" FROM "gbook_gradelevel" WHERE "gbook_gradelevel"."policy_id" = 6 ORDER BY "gbook_gradelevel"."score" DESC; args=(6,)

(0.001) SELECT "gbook_gradelevel"."id", "gbook_gradelevel"."name", "gbook_gradelevel"."score", "gbook_gradelevel"."abbreviation", "gbook_gradelevel"."policy_id" FROM "gbook_gradelevel" WHERE "gbook_gradelevel"."policy_id" = 7 ORDER BY "gbook_gradelevel"."score" DESC; args=(7,)

(0.001) SELECT "gbook_gradelevel"."id", "gbook_gradelevel"."name", "gbook_gradelevel"."score", "gbook_gradelevel"."abbreviation", "gbook_gradelevel"."policy_id" FROM "gbook_gradelevel" WHERE "gbook_gradelevel"."policy_id" = 8 ORDER BY "gbook_gradelevel"."score" DESC; args=(8,)


为了防止这种情况,我按照上面的代码进行预取。其结果是:

(0.002) SELECT "gbook_gradepolicy"."id", "gbook_gradepolicy"."name", "gbook_gradepolicy"."minscore", "gbook_gradepolicy"."maxscore", "gbook_gradepolicy"."type" FROM "gbook_gradepolicy" ORDER BY "gbook_gradepolicy"."id" ASC; args=()

(0.004) SELECT "gbook_gradelevel"."id", "gbook_gradelevel"."name", "gbook_gradelevel"."score", "gbook_gradelevel"."abbreviation", "gbook_gradelevel"."policy_id" FROM "gbook_gradelevel" WHERE "gbook_gradelevel"."policy_id" IN (2, 3, 4, 5, 6, 7, 8); args=(2, 3, 4, 5, 6, 7, 8)

(0.001) SELECT "gbook_gradelevel"."id", "gbook_gradelevel"."name", "gbook_gradelevel"."score", "gbook_gradelevel"."abbreviation", "gbook_gradelevel"."policy_id" FROM "gbook_gradelevel" WHERE "gbook_gradelevel"."policy_id" = 2 ORDER BY "gbook_gradelevel"."score" DESC; args=(2,)

(0.001) SELECT "gbook_gradelevel"."id", "gbook_gradelevel"."name", "gbook_gradelevel"."score", "gbook_gradelevel"."abbreviation", "gbook_gradelevel"."policy_id" FROM "gbook_gradelevel" WHERE "gbook_gradelevel"."policy_id" = 3 ORDER BY "gbook_gradelevel"."score" DESC; args=(3,)

(0.001) SELECT "gbook_gradelevel"."id", "gbook_gradelevel"."name", "gbook_gradelevel"."score", "gbook_gradelevel"."abbreviation", "gbook_gradelevel"."policy_id" FROM "gbook_gradelevel" WHERE "gbook_gradelevel"."policy_id" = 4 ORDER BY "gbook_gradelevel"."score" DESC; args=(4,)

(0.000) SELECT "gbook_gradelevel"."id", "gbook_gradelevel"."name", "gbook_gradelevel"."score", "gbook_gradelevel"."abbreviation", "gbook_gradelevel"."policy_id" FROM "gbook_gradelevel" WHERE "gbook_gradelevel"."policy_id" = 5 ORDER BY "gbook_gradelevel"."score" DESC; args=(5,)

(0.001) SELECT "gbook_gradelevel"."id", "gbook_gradelevel"."name", "gbook_gradelevel"."score", "gbook_gradelevel"."abbreviation", "gbook_gradelevel"."policy_id" FROM "gbook_gradelevel" WHERE "gbook_gradelevel"."policy_id" = 6 ORDER BY "gbook_gradelevel"."score" DESC; args=(6,)

(0.000) SELECT "gbook_gradelevel"."id", "gbook_gradelevel"."name", "gbook_gradelevel"."score", "gbook_gradelevel"."abbreviation", "gbook_gradelevel"."policy_id" FROM "gbook_gradelevel" WHERE "gbook_gradelevel"."policy_id" = 7 ORDER BY "gbook_gradelevel"."score" DESC; args=(7,)

(0.000) SELECT "gbook_gradelevel"."id", "gbook_gradelevel"."name", "gbook_gradelevel"."score", "gbook_gradelevel"."abbreviation", "gbook_gradelevel"."policy_id" FROM "gbook_gradelevel" WHERE "gbook_gradelevel"."policy_id" = 8 ORDER BY "gbook_gradelevel"."score" DESC; args=(8,)


我在这里看到了预取的结果((0.004) SELECT "gbook_gradelevel"."id", "gbook_gradelevel"."name", "gbook_gradelevel"."score", "gbook_gradelevel"."abbreviation", "gbook_gradelevel"."policy_id" FROM "gbook_gradelevel" WHERE "gbook_gradelevel"."policy_id" IN (2, 3, 4, 5, 6, 7, 8); args=(2, 3, 4, 5, 6, 7, 8)),但它仍然执行下面所有额外的查询。
我怎样才能摆脱N+1查询问题与此查找?

7xzttuei

7xzttuei1#

正是get_levels方法降低了API的速度。你不需要它,只需要像这样用你的GradeLevelSerializer替换它:

class GradePolicySerializer(serializers.HyperlinkedModelSerializer):
    url = serializers.HyperlinkedIdentityField(view_name="gbook:gradepolicies-detail")
    levels = GradeLevelSerializer(many=True, read_only=True)

    class Meta:
        model = GradePolicy
        fields = ['pk', 'url', 'name', 'minScore', 'maxScore', 'type', 'levels']

字符串
prefetch_related在viewset中很好,因为你正在做反向关系。
您也可以使用Prefetch在视图集中指定分数排序
默认情况下,DRF不会优化数据库命中以获得更多查看here
您也可以像这样更新反向关系的相关名称:

class GradeLevel(models.Model):
  ...
  policy = models.ForeignKey(GradePolicy, null=True, blank=True, on_delete=models.DO_NOTHING, related_name='levels')

相关问题