防止在 SerializerMethodField 中重复查询

Prevent repeating query within SerializerMethodField s

所以我的序列化程序是这样调用的:

result_serializer = TaskInfoSerializer(tasks, many=True)

和序列化器:

class TaskInfoSerializer(serializers.ModelSerializer):
    done_jobs_count = serializers.SerializerMethodField()
    total_jobs_count = serializers.SerializerMethodField()
    task_status = serializers.SerializerMethodField()

    class Meta:
        model = Task
        fields = ('task_id', 'task_name', 'done_jobs_count', 'total_jobs_count', 'task_status')

    def get_done_jobs_count(self, obj):
        qs  = Job.objects.filter(task__task_id=obj.task_id, done_flag=1)
        condition = False
        # Some complicate logic to determine condition that I can't reveal due to business
        result = qs.count() if condition else 0
        # this function take around 3 seconds
        return result

    def get_total_jobs_count(self, obj):
        qs = Job.objects.filter(task__task_id=obj.task_id)
        # this query take around 3-5 seconds
        return qs.count()

    def get_task_status(self, obj):
        done_count    = self.get_done_jobs_count(obj)
        total_count    = self.get_total_jobs_count(obj)
        if done_count >= total_count:
            return 'done'
        else:
            return 'not yet'

当 get_task_status 函数被调用时,它会调用其他 2 个函数并再次进行这 2 个昂贵的查询。 有什么最好的方法可以防止这种情况发生吗?而且我真的不知道要调用的那些函数的顺序,它是基于 Meta 字段中声明的顺序吗?还是更高?

编辑: get_done_jobs_count里面的逻辑有点复杂,在get task

的时候,我不能把它变成一个查询

编辑 2: 我只是将所有这些计数函数带入模型并使用 cached_property https://docs.djangoproject.com/en/2.1/ref/utils/#module-django.utils.functional 但它提出了另一个问题:这个数字可靠吗?我不太了解 django 缓存,是 cached_property 只存在于这个实例中(直到 API 得到任务列表 return 一个响应)或者它会存在一段时间?

使用 iterator() 并计算 Iterator 可能会解决您的问题。

job_iter = Job.objects.filter(task__task_id=obj.task_id).iterator()
count = len(list(job_iter))
return count

如果需要,您可以使用 select_related() 和 prefetch_related() 一次检索所有内容。

注意:如果你使用iterator()到运行查询,prefetch_related() 调用将被忽略

您可能需要查看 optimisation

的文档

您可以对这些值进行注释以避免进行额外查询。所以传递给序列化程序的查询集看起来像这样(它可能会根据您使用的 Django 版本和作业的相关查询名称而改变):

tasks = tasks.annotate(
    done_jobs=Count('jobs', filter=Q(done_flag=1)),
    total_jobs=Count('jobs'),
)
result_serializer = TaskInfoSerializer(tasks, many=True)

那么序列化器方法将如下所示:

def get_task_status(self, obj):
    if obj.done_jobs >= obj.total_jobs:
        return 'done'
    else:
        return 'not yet'

编辑:如果您必须为每个任务实例调用该方法(似乎是这种情况),cached_property 将无济于事。问题不在于计算,而在于您是否必须为每个单独的任务访问数据库。您必须专注于在单个查询中获取计算所需的所有信息。如果那不可能或太复杂,可以考虑更改数据结构(模型)以促进这一点。

我试了一下 cached_property 确实解决了问题。

型号:

from django.utils.functional import cached_property
from django.db import models

class Task(models.Model):
    task_id = models.AutoField(primary_key=True)
    task_name = models.CharField(default='')

    @cached_property
    def done_jobs_count(self):
        qs  = self.jobs.filter(done_flag=1)
        condition = False
        # Some complicate logic to determine condition that I can't reveal due to business
        result = qs.count() if condition else 0
        # this function take around 3 seconds
        return result

    @cached_property
    def total_jobs_count(self):
        qs = Job.objects.filter(task__task_id=obj.task_id)
        # this query take around 3-5 seconds
        return qs.count()

    @property
    def task_status(self):
        done_count    = self.done_jobs_count
        total_count    = self.total_jobs_count
        if done_count >= total_count:
            return 'done'
        else:
            return 'not yet'

序列化器:

class TaskInfoSerializer(serializers.ModelSerializer):

    class Meta:
        model = Task
        fields = ('task_id', 'task_name', 'done_jobs_count', 'total_jobs_count', 'task_status')