Django 1.11计算性能优化求助:5400行数据计算过慢
提速方案针对Django1.11+Python3.5+PostgreSQL场景
1. 消除N+1查询,用数据库聚合替代Python层面计算
当前循环内每次都拉取全量score数据到Python再做求和统计,IO开销极大。直接利用PostgreSQL聚合函数在数据库层面完成sum、count计算:
from django.db.models import Sum, Count # 循环内替换原查询逻辑 current_year_agg = models.Answer.objects.filter( year_filter=year, **combination_filter ).aggregate( total_score=Sum('score'), answer_count=Count('id') ) total_score = current_year_agg['total_score'] or 0 answer_count = current_year_agg['answer_count'] or 0 avg_score = int(total_score / answer_count) if answer_count > 0 else 0 history_agg = models.Answer.objects.filter( year_filter__lt=year, **combination_filter ).aggregate( total_history=Sum('score'), history_count=Count('id') ) total_history = history_agg['total_history'] or 0 history_count = history_agg['history_count'] or 0 avg_history = int(total_history / history_count) if history_count > 0 else 0
每个组合仅需2次聚合查询,数据库直接返回计算结果,无需拉取全量score数据。
2. 批量更新数据库,减少请求次数
当前循环内每次调用update()都会发起一次数据库请求,5400条数据会产生5400次请求。改用bulk_update批量更新:
# 初始化列表存储待更新实例 update_list = [] # 定义要更新的字段列表 update_fields = [ 'score', 'history', 'country_benchmark', 'industry_benchmark', 'hp_benchmark', 'dev0', 'dev1', 'dev2', 'dev3', 's_respondent_count', 'h_respondent_count', 'cb_respondent_count', 'ib_respondent_count', 'hp_respondent_count' ] for combination in ees_combinations: # ... 中间计算逻辑保持不变 ... # 构造待更新实例 combo_instance = models.Combination.objects.get(pk=combination['id']) for key, value in calculated_data.items(): setattr(combo_instance, key, value) update_list.append(combo_instance) # 每100条批量更新一次,避免内存溢出 if len(update_list) >= 100: models.Combination.objects.bulk_update(update_list, update_fields) update_list = [] # 处理剩余未更新的实例 if update_list: models.Combination.objects.bulk_update(update_list, update_fields)
Django1.11的bulk_update需要明确指定更新字段,这样能将数据库请求次数从5400次压缩到几十次。
3. 添加数据库索引,加速过滤查询
针对Answer表中频繁用于过滤的字段创建组合索引,大幅提升查询速度:
# 在Answer模型的Meta类中添加索引 class Answer(models.Model): # ... 现有字段定义 ... class Meta: indexes = [ models.Index( fields=['year_filter', 'age_group', 'gender', 'division', 'tenure', 'career_type', 'organization_filter', 'survey_filter'] ), ]
也可针对单个高频过滤字段(如year_filter、organization_filter)添加单独索引,帮助PostgreSQL快速定位目标数据。
4. 异步执行计算任务,避免前端响应阻塞
将计算逻辑放到后台异步执行,前端先收到任务启动的响应,无需等待计算完成:
- 安装适配Python3.5和Django1.11的Celery版本(如Celery4.4.x),配合Redis/RabbitMQ作为消息队列
- 定义异步任务:
# tasks.py from celery import shared_task from django.db.models import Sum, Count from . import models @shared_task def calculate_ees_data(survey, language, organization, year, answer, table): filters = { 'organization_filter': organization, 'survey_filter': survey, 'language_filter': language, 'year_filter': year, 'answer_filter': answer, 'table_filter': table } ees_combinations = models.Combination.objects.filter(**filters).values( 'id', 'demographic', 'gender', 'division', 'tenure', 'career_type_level' ).iterator() update_list = [] update_fields = [ 'score', 'history', 'country_benchmark', 'industry_benchmark', 'hp_benchmark', 'dev0', 'dev1', 'dev2', 'dev3', 's_respondent_count', 'h_respondent_count', 'cb_respondent_count', 'ib_respondent_count', 'hp_respondent_count' ] for combination in ees_combinations: # ... 聚合计算逻辑同方案1 ... # ... 构造calculated_data ... combo_instance = models.Combination.objects.get(pk=combination['id']) for key, value in calculated_data.items(): setattr(combo_instance, key, value) update_list.append(combo_instance) if len(update_list) >= 100: models.Combination.objects.bulk_update(update_list, update_fields) update_list = [] if update_list: models.Combination.objects.bulk_update(update_list, update_fields)
- 在视图中调用异步任务:
def ees_calculate(request): if request.user.is_superuser: if request.is_ajax() and request.method == 'POST': # ... 获取POST参数 ... calculate_ees_data.delay(survey, language, organization, year, answer, table) return JsonResponse({'result':'success', 'message':'计算任务已后台启动'}) # ... 其他权限校验逻辑 ...
5. 优化查询集,降低内存占用
将ees_combinations改为迭代器查询,避免一次性加载5400条数据到内存:
ees_combinations = models.Combination.objects.filter(**filters).values( 'id', 'demographic', 'gender', 'division', 'tenure', 'career_type_level' ).iterator()
iterator()会逐个从数据库获取数据,大幅降低内存消耗。
内容的提问来源于stack exchange,提问作者user18408844
相关产品推荐
相关产品推荐

