Django -解决一个可能的n + 1问题

6l7fqoea  于 2023-04-13  发布在  Go
关注(0)|答案(1)|浏览(101)

我的一个观点是,一个特定的查询会对数据库造成很大的冲击。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,所以我需要检查它是否存在。正如代码所示,我可能只想包含最新的对象。
有没有更便宜的方法来解决这个问题?

1l5u6lss

1l5u6lss1#

在每个classroom循环中,你都要进行额外的调用。第一个是.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(
        'grade_set', 
        queryset=grade_queryset,
    )
 )
#now loop through - django should use its cache for .all() 
for c in classrooms:
    c_grades_count = len(c.grade_set.all())
    if c_grades_count  < 3:
        qn = c.grades.all()[0]

相关问题