如何优化Django中的for循环及关联查询逻辑
Django 查询与循环性能优化方案
问题背景
现有基于Python和Django编写的代码运行耗时过长,核心是获取不同状态候选人属性的逻辑存在性能瓶颈,希望通过优化属性调用和查询逻辑来提升效率。
原实现代码
函数定义
cand_seleccionados = ListaFinal.objects.filter(interesado__id_oferta=efectiva.oferta.id) seleccionados_ids = cand_seleccionados.values_list("interesado_id", flat=True) cand_postulados = Postulados.objects.filter( interesado__id_oferta=efectiva.oferta.id ).exclude(interesado_id__in=seleccionados_ids) postulados_ids = cand_postulados.values_list("interesado_id", flat=True) cand_entrevistados = Entrevistados.objects.filter( interesado__id_oferta=efectiva.oferta.id ).exclude(interesado_id__in=postulados_ids)
循环逻辑(以cand_postulados为例)
for p in cand_postulados: postulado = dict() telefono = Perfil.objects.values_list("telefono", flat=True).get( user_id=p.interesado.candidato.id ) postulado["id"] = p.interesado.candidato.id postulado["nombre"] = p.interesado.candidato.first_name postulado["email"] = p.interesado.candidato.email postulado["teléfono"] = telefono if p.interesado.id_oferta.pais is None: postulado["pais"] = "Sin pais registrado" else: postulado["pais"] = p.interesado.id_oferta.pais.nombre postulado["nombre_reclutador"] = p.interesado.id_reclutador.first_name postulado["id_reclutador"] = p.interesado.id_reclutador.id postulados.append(postulado)
优化方案
1. 预加载关联对象,解决N+1查询问题
原代码循环中访问p.interesado.candidato、p.interesado.id_oferta等关联对象时,会触发大量额外数据库查询,这是典型的N+1性能问题。使用select_related(外键/一对一关联)和prefetch_related(多对多/反向关联)一次性加载所有需要的关联数据:
# 优化cand_postulados查询,预加载所有关联对象 cand_postulados = Postulados.objects.filter( interesado__id_oferta=efectiva.oferta.id ).exclude(interesado_id__in=seleccionados_ids).select_related( 'interesado', 'interesado__candidato', 'interesado__id_oferta', 'interesado__id_oferta__pais', 'interesado__id_reclutador' )
2. 批量获取Perfil数据,避免循环内查询
原循环中每次单独查询Perfil,改为一次性批量获取所有需要的Perfil数据,通过字典映射快速取值:
# 批量获取当前候选人的所有Perfil数据 candidato_ids = [p.interesado.candidato.id for p in cand_postulados] perfil_map = {p.user_id: p.telefono for p in Perfil.objects.filter(user_id__in=candidato_ids).only('user_id', 'telefono')} # 循环内直接从字典取数据,无需再查数据库 for p in cand_postulados: postulado = dict() candidato_id = p.interesado.candidato.id postulado["id"] = candidato_id postulado["nombre"] = p.interesado.candidato.first_name postulado["email"] = p.interesado.candidato.email postulado["teléfono"] = perfil_map.get(candidato_id, "") # 处理无手机号的情况 oferta_pais = p.interesado.id_oferta.pais postulado["pais"] = oferta_pais.nombre if oferta_pais else "Sin pais registrado" postulado["nombre_reclutador"] = p.interesado.id_reclutador.first_name postulado["id_reclutador"] = p.interesado.id_reclutador.id postulados.append(postulado)
3. 合并查询逻辑,减少数据库交互次数
原代码多次查询不同状态的候选人,可通过一次查询获取所有关联ID后在内存中分类,或用Q对象简化过滤:
from django.db.models import Q # 一次性获取该职位下各状态的interesado ID集合 seleccionado_ids = set(ListaFinal.objects.filter(interesado__id_oferta=efectiva.oferta.id).values_list('interesado_id', flat=True)) postulado_ids = set(Postulados.objects.filter(interesado__id_oferta=efectiva.oferta.id).values_list('interesado_id', flat=True)) - seleccionado_ids entrevistado_ids = set(Entrevistados.objects.filter(interesado__id_oferta=efectiva.oferta.id).values_list('interesado_id', flat=True)) - seleccionado_ids - postulado_ids # 后续查询基于这些ID集合,减少重复过滤的数据库开销
4. 使用values()直接获取所需字段,减少对象实例化开销
如果不需要完整的模型对象,直接用values()在数据库层面查询所需字段,避免创建大量模型实例,节省内存和时间:
cand_postulados = Postulados.objects.filter( interesado__id_oferta=efectiva.oferta.id ).exclude(interesado_id__in=seleccionados_ids).values( 'interesado__candidato__id', 'interesado__candidato__first_name', 'interesado__candidato__email', 'interesado__id_oferta__pais__nombre', 'interesado__id_reclutador__first_name', 'interesado__id_reclutador__id' ) # 循环直接从字典取值,无需访问关联对象 for item in cand_postulados: postulado = { "id": item['interesado__candidato__id'], "nombre": item['interesado__candidato__first_name'], "email": item['interesado__candidato__email'], "pais": item['interesado__id_oferta__pais__nombre'] or "Sin pais registrado", "nombre_reclutador": item['interesado__id_reclutador__first_name'], "id_reclutador": item['interesado__id_reclutador__id'] } postulados.append(postulado)
关键优化点总结
- 用
select_related/prefetch_related预加载关联数据,彻底解决N+1查询问题 - 批量查询关联数据,避免循环内发起数据库请求
- 合并查询逻辑,减少与数据库的交互次数
- 优先使用
values()/values_list()获取所需字段,减少模型实例化开销
内容的提问来源于stack exchange,提问作者Manu Tapia
相关产品推荐
相关产品推荐

