Django页面渲染耗时约2秒的原因及优化方案咨询
为何处理少量数据时页面渲染仍耗时久?如何解决?
现象
页面渲染耗时约2秒,即使返回模拟数据仍缓慢,但返回空字典tasks_by_day时仅需约150ms。已测得tasks函数执行时间约110ms。
视图代码
def tasks(request): tasks = Task.objects.filter(owner=request.user.id) tasks_by_day = { "MONDAY" : [tasks[0]], "TUESDAY" : [tasks[1]], "WEDNESDAY": [tasks[2]], "THURSDAY": [tasks[3]], "FRIDAY": [tasks[4]], "SATURDAY": [tasks[5]], "SUNDAY": [tasks[7]] } return render(request, "todo/tasks.html", {"tasks_by_day": tasks_by_day})
模型代码
class Task(models.Model): # DAY_OF_THE_WEEK_CHOICES, STATUS_CHOICES - constants day_of_the_week = models.CharField( max_length=10, choices=DAY_OF_THE_WEEK_CHOICES, default=default_day ) status = models.CharField( max_length=2, choices=STATUS_CHOICES, default=NOT_COMPLETED ) task_text = models.CharField(max_length=200) owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
问题根源
核心问题出在多次触发数据库查询:
Task.objects.filter(...)返回的是Django的QuerySet,它是惰性执行的。当你用tasks[0]、tasks[1]这种索引访问时,每一次索引都会单独执行一次SQL查询来获取对应的数据。你这里一共访问了7次索引,加上初始的filter查询,总共8次数据库请求,大量的IO往返导致了耗时剧增。- 另外,硬编码索引的逻辑完全不合理——任务应该按自身的
day_of_the_week字段分组,而不是按查询结果的顺序强行分配到星期几,这不仅逻辑错误,还可能在任务数量不足时抛出IndexError。
解决方案
1. 一次性获取所有数据,避免重复查询
先把QuerySet转换成列表,这样只执行一次数据库查询,后续操作都基于内存中的列表:
def tasks(request): # 用list()强制执行查询,将所有数据加载到内存 tasks = list(Task.objects.filter(owner=request.user.id)) # 加入长度判断,避免任务数量不足时抛出索引错误 tasks_by_day = { "MONDAY" : [tasks[0]] if len(tasks)>=1 else [], "TUESDAY" : [tasks[1]] if len(tasks)>=2 else [], "WEDNESDAY": [tasks[2]] if len(tasks)>=3 else [], "THURSDAY": [tasks[3]] if len(tasks)>=4 else [], "FRIDAY": [tasks[4]] if len(tasks)>=5 else [], "SATURDAY": [tasks[5]] if len(tasks)>=6 else [], "SUNDAY": [tasks[7]] if len(tasks)>=8 else [] } return render(request, "todo/tasks.html", {"tasks_by_day": tasks_by_day})
2. 按任务自身的星期字段正确分组(推荐)
抛弃硬编码索引,根据day_of_the_week字段自动分组,这才符合业务逻辑,同时避免多次查询:
from collections import defaultdict def tasks(request): # 一次查询获取所有用户任务 tasks = Task.objects.filter(owner=request.user.id) # 初始化分组字典,自动处理空列表 tasks_by_day = defaultdict(list) for task in tasks: # 按任务所属的星期添加到对应分组 tasks_by_day[task.day_of_the_week].append(task) # 确保所有星期键都存在,防止模板中找不到键报错 week_days = ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"] for day in week_days: if day not in tasks_by_day: tasks_by_day[day] = [] return render(request, "todo/tasks.html", {"tasks_by_day": tasks_by_day})
3. 数据库层面分组(大数据量场景优化)
如果用户任务数量较多,可以让数据库直接完成分组,减少Python端的循环处理:
import django.db.models.aggregates def tasks(request): # 定义所有星期的默认结构 tasks_by_day = { "MONDAY": [], "TUESDAY": [], "WEDNESDAY": [], "THURSDAY": [], "FRIDAY": [], "SATURDAY": [], "SUNDAY": [] } # 按day_of_the_week分组查询,一次性获取每个星期的任务ID集合 grouped_tasks = Task.objects.filter(owner=request.user.id).values("day_of_the_week").annotate( task_ids=django.db.models.aggregates.Collect('id') ) # 填充分组数据 for group in grouped_tasks: day = group["day_of_the_week"] # 根据ID批量获取任务对象 tasks = Task.objects.filter(id__in=group["task_ids"]) tasks_by_day[day] = list(tasks) return render(request, "todo/tasks.html", {"tasks_by_day": tasks_by_day})
4. 模板辅助优化
如果模板中需要访问Task的关联字段(比如owner的属性),要提前用select_related预取关联数据,避免模板中触发额外查询:
# 预取owner字段,避免模板中访问task.owner时触发新查询 tasks = Task.objects.filter(owner=request.user.id).select_related('owner')
内容的提问来源于stack exchange,提问作者Munewxar
相关产品推荐
相关产品推荐

