Django千万级Task数据集加载缓慢,寻求优化方案
解决Django中800万条Task_Submitted数据的视图与Admin加载缓慢问题
问题背景
我有一个包含超800万条数据的Task_Submitted模型,访问Django Admin的对象页面和自定义视图时加载极慢,有时甚至超时。项目部署在AWS Elastic Beanstalk上,已经把数据库从db.t2.medium升级到db.t3.medium,但问题依旧。我把数据同步到本地后用Django Debug Tool分析,还是找不到明确的优化方向——哪怕是按status字段常规过滤数据,速度依然很慢。
慢加载代码片段
def view(): context['total_task_amount'] = Task_Submitted.objects.all().count() context['total_task_approved_amount'] = Task_Submitted.objects.filter(status = 'approved').count() context['total_task_pending_amount'] = Task_Submitted.objects.filter(status = 'pending').count() context['total_task_rejected_amount'] = Task_Submitted.objects.filter(status = 'rejected').count() context['total_task_cancel_amount'] = Task_Submitted.objects.filter(status = 'cancel').count() def s_view(): tasks_approved_objects = tasks_submited_query.filter(status = 'approved') tasks_rejected_objects = tasks_submited_query.filter(status = 'rejected') tasks_cancel_objects = tasks_submited_query.filter(status = 'cancel') tasks_pending_objects = tasks_submited_query.filter(status = 'pending').order_by('date_submitted')
优化方案
1. 合并统计查询,减少数据库交互
原来的5次count()查询可以合并成一次聚合查询,直接从数据库拿到所有统计结果,避免多次连接数据库的开销:
from django.db.models import Count, Case, When, IntegerField def view(): # 一次查询获取所有统计数据 stats = Task_Submitted.objects.aggregate( total=Count('id'), approved=Count(Case(When(status='approved', then=1), output_field=IntegerField())), pending=Count(Case(When(status='pending', then=1), output_field=IntegerField())), rejected=Count(Case(When(status='rejected', then=1), output_field=IntegerField())), cancel=Count(Case(When(status='cancel', then=1), output_field=IntegerField())) ) # 给context赋值 context['total_task_amount'] = stats['total'] context['total_task_approved_amount'] = stats['approved'] context['total_task_pending_amount'] = stats['pending'] context['total_task_rejected_amount'] = stats['rejected'] context['total_task_cancel_amount'] = stats['cancel']
2. 给高频过滤/排序字段加索引
- 给
status字段加普通索引:因为大量过滤和统计都依赖这个字段,直接在模型里配置:
class Task_Submitted(models.Model): status = models.CharField(max_length=20, db_index=True) # 其他字段...
- 给
status + date_submitted加联合索引:s_view里过滤status='pending'后还要按date_submitted排序,联合索引能让数据库直接用索引完成排序,避免额外的排序操作:
class Task_Submitted(models.Model): status = models.CharField(max_length=20, db_index=True) date_submitted = models.DateTimeField() class Meta: indexes = [ models.Index(fields=['status', 'date_submitted']), ]
如果数据库支持,还可以创建覆盖索引(包含查询所需的所有字段),避免回表查询,进一步提升速度。
3. 优化查询集处理
- 在
s_view里,避免多次过滤同一个基础查询集,如果需要遍历大量数据,用iterator()减少内存占用:
def s_view(): # 若需遍历对象,用iterator()分批加载,避免一次性把800万条数据读进内存 tasks_pending_objects = tasks_submited_query.filter(status='pending').order_by('date_submitted').iterator() # 其他状态的查询同理,按需处理
- 不要在视图中直接返回全部对象列表,尤其是Admin页面,一定要分页。
4. 缓存统计类数据
统计数据不会实时变化的话,用Django缓存(比如Redis/Memcached)缓存结果,不用每次请求都查数据库:
from django.core.cache import cache def view(): cache_key = 'task_submitted_stats' stats = cache.get(cache_key) if not stats: stats = Task_Submitted.objects.aggregate( total=Count('id'), approved=Count(Case(When(status='approved', then=1), output_field=IntegerField())), pending=Count(Case(When(status='pending', then=1), output_field=IntegerField())), rejected=Count(Case(When(status='rejected', then=1), output_field=IntegerField())), cancel=Count(Case(When(status='cancel', then=1), output_field=IntegerField())) ) # 缓存5分钟,可根据业务调整过期时间 cache.set(cache_key, stats, 300) # 赋值到context
5. Django Admin页面专项优化
- 在
Task_SubmittedAdmin里限制每页显示数量,禁用不必要的功能:
from django.contrib import admin class Task_SubmittedAdmin(admin.ModelAdmin): list_per_page = 20 # 减少每页加载的数据量 list_display = ('id', 'status', 'date_submitted') # 只显示必要字段 # 禁用不需要的批量操作 actions = None # 外键字段用raw_id_fields,避免加载大量选项 raw_id_fields = ('related_model',)
- 避免使用低效的
list_filter,如果必须加过滤器,用RelatedOnlyFieldListFilter或者自定义基于索引的过滤器。
6. 数据库层面优化
- 开启AWS RDS的查询缓存,调整连接池参数,避免连接耗尽;
- 查看数据库慢查询日志,用
EXPLAIN分析慢SQL的执行计划,确认是否存在全表扫描、索引未命中的情况; - 若数据量持续增长,可考虑分表或分区(比如按日期分区),把历史数据迁移到单独的表,减少单表数据量。
内容的提问来源于stack exchange,提问作者Shittu Abdulrasheed Ayomide
相关产品推荐
相关产品推荐

