如何在Django中缓存数据库表?解决重复查询性能问题
解决Django中重复数据库查询的缓存实现方案
针对无外键关联导致大量重复查询的问题,以下是几种具体的实现方案:
一、手动批量预取关联数据(推荐优先使用)
这种方法从根源减少查询次数,将关联表数据一次性查询后存入字典,序列化器直接从字典取值,完全避免重复查询。
修改视图代码:
class FileDownloaderSerializer(APIView): def get(self, request, **kwargs): filename = "All-users.csv" # 1. 查询所有用户数据 datas = Userstable.objects.using(dbname).all() # 2. 批量预取所有需要的关联表数据,转成ID映射字典 # 预取section表数据 section_map = { s.id: s.sectionname for s in section.objects.using(dbname).all() } # 预取department表数据(修正原代码中表名混淆问题) dept_map = { d.id: d.deptname for d in department.objects.using(dbname).all() } # 3. 将映射字典传入序列化器上下文 serializer = UserSerializer( datas, context={'sector': sector, 'section_map': section_map, 'dept_map': dept_map}, many=True ) df = serializer.data # 优化文件操作:使用with语句自动关闭文件 with open(filename, 'w') as f: df.to_csv(f, index=False, header=False) wrapper = FileWrapper(open(filename)) response = HttpResponse(wrapper, content_type='text/csv') response['Content-Length'] = os.path.getsize(filename) response['Content-Disposition'] = f"attachment; filename={filename}" return response
修改序列化器代码:
class UserSerializer(serializers.ModelSerializer): class Meta: model = Userstable fields = '__all__' section = serializers.SerializerMethodField() department = serializers.SerializerMethodField() def get_section(self, obj): # 从上下文字典直接取值,无需查询数据库 return self.context['section_map'].get(obj.sectionid, '') def get_department(self, obj): return self.context['dept_map'].get(obj.deptid, '')
效果:原本500次查询会减少到3次(用户表1次、section表1次、department表1次),彻底解决重复查询问题。
二、使用Django缓存框架缓存单条关联数据
如果关联表数据更新频率低,可以用Django内置缓存将单条关联数据缓存起来,避免重复查询。
修改序列化器代码:
from django.core.cache import cache class UserSerializer(serializers.ModelSerializer): class Meta: model = Userstable fields = '__all__' section = serializers.SerializerMethodField() department = serializers.SerializerMethodField() def get_section(self, obj): # 加入数据库标识避免多库缓存冲突 cache_key = f"section_{dbname}_name_{obj.sectionid}" # 先从缓存取数据 section_name = cache.get(cache_key) if not section_name: # 缓存不存在时查询数据库并写入缓存 section_obj = section.objects.using(dbname).get(pk=obj.sectionid) section_name = section_obj.sectionname # 设置缓存过期时间(例如1小时,可根据数据更新频率调整) cache.set(cache_key, section_name, timeout=3600) return section_name def get_department(self, obj): cache_key = f"dept_{dbname}_name_{obj.deptid}" dept_name = cache.get(cache_key) if not dept_name: dept_obj = department.objects.using(dbname).get(pk=obj.deptid) dept_name = dept_obj.deptname cache.set(cache_key, dept_name, timeout=3600) return dept_name
注意:需要先配置Django缓存(如Redis、Memcached或本地内存缓存),如果数据频繁更新,需在数据修改时主动删除对应缓存键,保证缓存一致性。
三、视图层全局内存缓存(单次请求复用)
如果只需要在生成CSV的单次请求内复用数据,可在视图初始化时加载所有关联表数据,序列化器直接调用,适合多表复杂场景:
视图代码示例:
class FileDownloaderSerializer(APIView): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 初始化时加载所有关联表数据到内存 self.section_map = {s.id: s.sectionname for s in section.objects.using(dbname).all()} self.dept_map = {d.id: d.deptname for d in department.objects.using(dbname).all()} # 其他关联表同理扩展 self.role_map = {r.id: r.rolename for r in Role.objects.using(dbname).all()} def get(self, request, **kwargs): filename = "All-users.csv" datas = Userstable.objects.using(dbname).all() serializer = UserSerializer( datas, context={ 'sector': sector, 'section_map': self.section_map, 'dept_map': self.dept_map, 'role_map': self.role_map }, many=True ) df = serializer.data with open(filename, 'w') as f: df.to_csv(f, index=False, header=False) wrapper = FileWrapper(open(filename)) response = HttpResponse(wrapper, content_type='text/csv') response['Content-Length'] = os.path.getsize(filename) response['Content-Disposition'] = f"attachment; filename={filename}" return response
优势:单次请求内所有序列化逻辑复用内存中的数据,无额外数据库查询,性能最优。
长远建议
如果条件允许,建议补设数据库外键,之后可以使用Django的select_related或prefetch_related自动优化查询,代码会更简洁易维护。
内容的提问来源于stack exchange,提问作者user18489452
相关产品推荐
相关产品推荐

