我的一个观点是使用特定查询多次访问数据库。 Scout-apm 将其识别为可能的 n+1 查询。我不确定这是问题所在,但它仍然是一个问题。
我原来的代码是:
models.py
class Grade(models.Model):
score = models.CharField(max_length=4, blank=True)
testing = models.ForeignKey(Testing, on_delete=models.CASCADE)
student = models.ForeignKey(Student, on_delete=models.CASCADE)
classroom = models.ForeignKey(Purpose, on_delete=models.CASCADE)
time_created = models.DateField(
auto_now=False, auto_now_add=False, default=timezone.now)
Class Testing(models.Model):
name = models.CharField(max_length=20, blank=True)
omit = models.BooleanField(default=False)
Class Classroom(models.Model):
name = models.CharField(max_length=20, blank=True)
views.py
def allgrades(request, student_id):
classrooms = Classroom.objects.all()
for c in classrooms:
q = Grade.objects.filter(
student=student_id, classroom=c.id, testing__omit="False").order_by('-time_created')
if q.exists():
if len(q) < 3:
qn = q.first()
else:
....
违规查询来自
if q.exists():
和qn = q.first()
。我不知道这是否属于 n + 1 的类别,但是如果我去掉 if q.exists():
和 qn = q.first()
,查询数量会从 1300 左右减少到 400 左右
我需要这些语句的功能。因为我正在用
q
做一些事情,所以我需要检查它是否存在。正如代码所示,我可能只想包含最新的对象。
有没有更便宜的方法来处理这个问题?
你正在通过教室的每个循环进行额外的呼叫。第一个是 .exists(),查看那里是否有东西的成本很低,但如果有并且你需要调用它会变得有点多余,因为检索实际记录是另一个调用(所以最多 2 * num_classrooms).
如果您预取所有内容,则根本不需要进行许多额外的调用:
#set up your grade queryset for clarity
grade_queryset = Grade.objects.filter(
student=student_id, testing__omit="False"
).order_by('-time_created')
#get all the Classrooms and their associated grades
classrooms = Classroom.object.prefetch_related(
Prefetch(
'grades',
queryset=grade_queryset,
)
)
#now loop through - django should use its cache for .all() and .first()
for c in classrooms:
c_grades_count = len(c.grades.all())
if c_grades_count < 3:
qn = c.grades.all()[0]