防止在 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')
所以我的序列化程序是这样调用的:
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')